1. 程式人生 > 實用技巧 >Spring Cloud (二):服務發現 Eureka 和 Consul

Spring Cloud (二):服務發現 Eureka 和 Consul

Eureka

Netflix 開發的元件,使用 Eureka 可以配合其他 Netflix 的元件比如 API 閘道器 ZUUL

2.0 起不再開發維護了
https://github.com/Netflix/eureka/wiki

The existing open source work on eureka 2.0 is discontinued. The code base and artifacts that were released as part of the existing repository of work on the 2.x branch is considered use at your own risk.

Eureka 1.x is a core part of Netflix's service discovery system and is still an active project.

Server

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
@EnableEurekaServer
@SpringBootApplication
public class Demo1Application {
	public static void main(String[] args) {
		SpringApplication.run(Demo1Application.class, args);
	}
}
server.port=8761
eureka.instance.hostname=localhost
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8761/eureka/

一個 Client 程式向 Server 註冊自己的資訊

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
	</properties>

	<dependencies>
	    <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
server.port=8762

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.client.healthcheck.enabled=true
spring.application.name=service-provider-1
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaClientApplication.class, args);
	}
}
@RestController
public class Controller {
	@RequestMapping("/api")
	public String index() {
		return "Greetings from Eureka Client!";
	}
}

登上 Eureka Server 的 URL http://localhost:8761/ 可以看到註冊了一個服務,這個服務的埠名字是 service-provider-1,埠是 8762,以及有這個服務對應的 IP

再寫一個程式,通過 Eureka Server 找到上面這個服務

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
	</properties>

	<dependencies>
	    <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
server.port=8763
## 如果 Eureka Server 使用預設的 8761 埠,並且這個程式不註冊,就不需要寫 Eureka 資訊
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaConsumerApplication.class, args);
	}
}
@RestController
public class Controller {
	@Autowired
    private DiscoveryClient discoveryClient;
 
    @RequestMapping("/queryService")
    public String query() {
        List<ServiceInstance> instances = discoveryClient.getInstances("service-provider-1");
        StringBuilder urls= new StringBuilder();
        for(ServiceInstance instance : instances){
            urls.append(instance.getHost()+":"+instance.getPort()).append(",");
        }
        return "service name : service-provider-1<br>" + "host : " + urls.toString();
    }
}

查詢 http://localhost:8763/queryService 可以看到返回

service name : service-provider-1
host : 192.168.0.101:8762,

Consul

Consul 是 HashiCorp 公司推出的開源工具,用於實現分散式系統的服務發現與配置
https://www.consul.io/docs/intro

與 Eureka 相比,Consul 提供更完整的功能,包括了

  • 服務發現,和 Eureka 類似
  • 健康檢查,比如定期檢查某個服務是否返回 200,檢查當前節點的記憶體使用率是否超過 90% 等
  • Key-Value 儲存,可用於動態配置,功能標記,leader 選舉,一致性協定,等等
  • 安全服務通訊,可以產生分發安全認證給服務,建立雙向安全連結
  • 多資料中心

和 Eureka 是 Spring Boot 實現不同,Consul 是通過 go 語言編寫的

安裝 Consul

sudo apt-get install consul

debug 環境可以用 -dev 只啟動一個用就可以

## 如果機器有多個 IP 就需要用 bind 指定一個進行繫結
consul agent -dev -bind=10.0.2.1

啟動後就可以訪問 http://localhost:8500

生產環境下應該啟動 Consul 叢集,叢集包括 Server 和 Client

Server 的數量是有限的若干個,比如 3 個,5 個,通常為奇數,方便選舉,Server 負責資料的儲存和同步,可能是為了防止每個服務都直接和 Server 打交道造成 Server 壓力太大,又為每臺跑服務的機器安裝 Client,因為每臺機都有,所以 Client 可以有很多很多個,Client 不儲存資料,只是將本機的服務請求、服務狀態,整合然後統一和 Server 進行互動

而每個服務實際上是在和本機的 Consul Client 打交道,Consul Server 對它們是透明的

這裡的 Server、Client 和平時理解的可能有點區別,可能叫 Consul Server 和 Consul Agent/Proxy 比較合適,但在這裡 Server 和 Client 都被稱為 Agent

假設起 3 個 server 和 1 個 client

consul agent -server -ui -bootstrap-expect=3 -node=s1 \
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d \
                     -bind=10.0.2.15 -client=0.0.0.0
consul agent -server -ui -bootstrap-expect=3 -node=s2 \
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d \
                     -bind=10.0.2.16 -client=0.0.0.0 -join=10.0.2.15
consul agent -server -ui -bootstrap-expect=3 -node=s3 \
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d \
                     -bind=10.0.2.17 -client=0.0.0.0 -join=10.0.2.15
consul agent -node=c1 \
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d \
                     -bind=10.0.2.18 -client=0.0.0.0 -join=10.0.2.15

可以檢視叢集成員

consul members

退出叢集

consul leave

註冊服務

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-consul-discovery</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>
server.port=8501
spring.application.name=spring-cloud-consul-service-1
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 註冊到 consul 的服務名稱
spring.cloud.consul.discovery.serviceName=consul-service-1
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConsulServiceApplication.class, args);
	}
}
@RestController
public class Controller {
    @RequestMapping("/hello")
    public String hello() {
        return "consul-service-1";
    }
}
# 實現另一個 service,註冊的名字一樣,但埠(或 IP)不一樣,這樣可以在消費端實現負載均衡
server.port=8502
spring.application.name=spring-cloud-consul-service-1
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 註冊到 consul 的服務名稱
spring.cloud.consul.discovery.serviceName=consul-service-1

消費服務

pom 檔案參考 service-1
spring.application.name=spring-cloud-consul-consumer
server.port=8503
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
# 設定不需要註冊到 consul 中
spring.cloud.consul.discovery.register=false
@SpringBootApplication
public class ConsulConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConsulConsumerApplication.class, args);
	}
}
@RestController
public class ServiceController {
    @Autowired
    private LoadBalancerClient loadBalancer;
    @Autowired
    private DiscoveryClient discoveryClient;
 
    @RequestMapping("/services")
    public Object services() {
        // 獲取 consul-service-1 服務的資訊
        // 這裡會返回兩個,因為有兩個服務註冊了這個名字
        return discoveryClient.getInstances("consul-service-1");
    }
 
    @RequestMapping("/discover")
    public Object discover() {
        // 通過 LoadBalancerClient 獲取 consul-service-1 服務的其中一個 host
        // 可以看到有時返回 8501 埠,有時返回 8502 埠,這樣就實現了負載均衡
        return loadBalancer.choose("consul-service-1").getUri().toString();
    }
}

CAP(C:一致性,A:可用性,P:分割槽容錯性)理論:分散式系統只能滿足 CAP 的其中兩個

P 是必須保證的
保證 C 就要保證同步,那麼同步沒完成就不能用,這樣就保證不了 A
保證 A 就必須立即返回結果,那麼就不能等同步完成,這樣就保證不了 C

Consul 屬於 CP,優先保證一致性
Eureka 屬於 AP,優先保證可用性

ZooKeeper(CP)、etcd(CP) 一定程度上也提供了分散式協調的功能