Netflix Eureka 2.X https://github.com/Netflix/eureka/wiki 官方宣告停止開發(fā),但其實對國內(nèi)的用戶影響甚小,一方面國內(nèi)大都使用的是 Eureka 1.X 系列,并且官方也在積極維護 1.X https://github.com/Netflix/eureka/releases。
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.
翻譯:
有關 eureka 2.0 的現(xiàn)有開源工作已停止。在 2.x 分支上作為現(xiàn)有工作資料庫的一部分發(fā)布的代碼庫和工件被視為使用后果自負。
Eureka 1.x 是 Netflix 服務發(fā)現(xiàn)系統(tǒng)的核心部分,仍然是一個活躍的項目。
雖然 Eureka,Hystrix 等不再繼續(xù)開發(fā)或維護,但是目前來說不影響使用,不管怎么說感謝開源,向 Netflix 公司的開源致敬。
另一方面 Spring Cloud 支持很多服務發(fā)現(xiàn)的軟件,Eureka 只是其中之一,比如我們今天要講的主角 Consul。下面是 Spring Cloud 支持的服務發(fā)現(xiàn)軟件以及特性對比。
常見的注冊中心
- Netflix Eureka
- Alibaba Nacos
- HashiCorp Consul
- Apache ZooKeeper
- CoreOS Etcd
- CNCF CoreDNS
| 特性 | Eureka | Nacos | Consul | Zookeeper |
|---|---|---|---|---|
| CAP | AP | CP + AP | CP | CP |
| 健康檢查 | Client Beat | TCP/HTTP/MYSQL/Client Beat | TCP/HTTP/gRPC/Cmd | Keep Alive |
| 雪崩保護 | 有 | 有 | 無 | 無 |
| 自動注銷實例 | 支持 | 支持 | 不支持 | 支持 |
| 訪問協(xié)議 | HTTP | HTTP/DNS | HTTP/DNS | TCP |
| 監(jiān)聽支持 | 支持 | 支持 | 支持 | 支持 |
| 多數(shù)據(jù)中心 | 支持 | 支持 | 支持 | 不支持 |
| 跨注冊中心同步 | 不支持 | 支持 | 支持 | 不支持 |
| SpringCloud集成 | 支持 | 支持 | 支持 | 支持 |
Consul 介紹
Consul 是 HashiCorp 公司推出的開源工具,用于實現(xiàn)分布式系統(tǒng)的服務發(fā)現(xiàn)與配置。與其它分布式服務注冊與發(fā)現(xiàn)的方案,Consul 的方案更“一站式”,內(nèi)置了服務注冊與發(fā)現(xiàn)框架、分布一致性協(xié)議實現(xiàn)、健康檢查、Key/Value 存儲(配置中心)、多數(shù)據(jù)中心方案,不再需要依賴其它工具(比如 ZooKeeper 等),使用起來也較為簡單。
Consul 使用 Go 語言編寫,因此具有天然可移植性(支持Linux、Windows 和 Mac OS);安裝包僅包含一個可執(zhí)行文件,方便部署,與 Docker 等輕量級容器可無縫配合。
Consul 特性
Raft 算法
服務發(fā)現(xiàn)
健康檢查
Key/Value 存儲(配置中心)
多數(shù)據(jù)中心
支持 http 和 dns 協(xié)議接口
官方提供 web 管理界面
Consul 角色
點擊鏈接觀看:Consul 角色視頻(獲取更多請關注公眾號「哈嘍沃德先生」)
- client:客戶端,無狀態(tài),將 HTTP 和 DNS 接口請求轉(zhuǎn)發(fā)給局域網(wǎng)內(nèi)的服務端集群。
- server:服務端,保存配置信息,高可用集群,每個數(shù)據(jù)中心的 server 數(shù)量推薦為 3 個或者 5 個。

首先,圖中有兩個數(shù)據(jù)中心,分別為 Datacenter1 和 Datacenter2 。Consul 非常好的支持多個數(shù)據(jù)中心,每個數(shù)據(jù)中心內(nèi),有客戶端和服務器端,服務器一般為 3~5 個,這樣可以在穩(wěn)定和性能上達到平衡,因為更多的機器會使數(shù)據(jù)同步很慢。不過客戶端是沒有限制的,可以有成千上萬個。
數(shù)據(jù)中心內(nèi)的所有節(jié)點都會加入到 Gossip (流言)協(xié)議。這就意味著有一個 Gossip 池,其中包含這個數(shù)據(jù)中心所有的節(jié)點??蛻舳瞬恍枰ヅ渲梅掌鞯刂沸畔?,發(fā)現(xiàn)服務工作會自動完成。檢測故障節(jié)點的工作不是放在服務器端,而是分布式的;這使得失敗檢測相對于本地化的心跳機制而言,更具可拓展性。在選擇 leader 這種重要的事情發(fā)生的時候,數(shù)據(jù)中心被用作消息層來做消息廣播。
每個數(shù)據(jù)中心內(nèi)的服務器都是單個 Raft 中節(jié)點集的一部分。這意味著他們一起工作,選擇一個單一的領導者——一個具有額外職責的選定的服務器。leader 負責處理所有查詢和事物。事物也必須作為同步協(xié)議的一部分復制到節(jié)點集中的所有節(jié)點。由于這個要求,當非 leader 服務器接收到 RPC 請求時,就會將請求其轉(zhuǎn)發(fā)給集群 leader。
服務器端節(jié)點同時也作為 WAN Gossip 池的一部分,WAN 池和 LAN 池不同的是,它針對網(wǎng)絡高延遲做了優(yōu)化,而且只包含其他Consul 服務器的節(jié)點。這個池的目的是允許數(shù)據(jù)中心以最少的消耗方式發(fā)現(xiàn)對方。啟動新的數(shù)據(jù)中心與加入現(xiàn)有的 WAN Gossip 一樣簡單。因為這些服務器都在這個池中運行,它還支持跨數(shù)據(jù)中心請求。當服務器收到對不同數(shù)據(jù)中心的請求時,它會將其轉(zhuǎn)發(fā)到正確數(shù)據(jù)中心中的隨機服務器。那個服務器可能會轉(zhuǎn)發(fā)給本地的 leader。
這樣會使數(shù)據(jù)中心的耦合非常低。但是由于故障檢測,連接緩存和復用,跨數(shù)據(jù)中心請求相對快速可靠。
總的來說,數(shù)據(jù)不會在不同的數(shù)據(jù)中心之間做復制備份。當收到一個請求處于別的數(shù)據(jù)中心的資源時,本地的 Consul 服務器會發(fā)一個 RPC 請求到遠端的 Consul 服務器,然后返回結果。如果遠端數(shù)據(jù)中心處于不可用狀態(tài),那么這么資源也會不可用,但這不影響本地的數(shù)據(jù)中心。在一些特殊的情況下,有限的數(shù)據(jù)集會被跨數(shù)據(jù)中心復制備份,比如說 Consul 內(nèi)置的 ACL 復制能力,或者像 consul-replicate 這樣的外部工具。
Consul 工作原理

服務發(fā)現(xiàn)以及注冊
當服務 Producer 啟動時,會將自己的 Ip/host 等信息通過發(fā)送請求告知 Consul,Consul 接收到 Producer 的注冊信息后,每隔 10s(默認)會向 Producer 發(fā)送一個健康檢查的請求,檢驗 Producer 是否健康。
服務調(diào)用
當 Consumer 請求 Product 時,會先從 Consul 中拿到存儲 Product 服務的 IP 和 Port 的臨時表(temp table),從temp table 表中任選一個· Producer 的 IP 和 Port, 然后根據(jù)這個 IP 和 Port,發(fā)送訪問請求;temp table 表只包含通過了健康檢查的 Producer 信息,并且每隔 10s(默認)更新。
Consul 安裝
Eureka 其實就是個 Servlet 程序,跑在 Servlet 容器中;Consul 則是用 go 語言編寫的第三方工具需要單獨安裝使用。
下載
訪問 Consul 官網(wǎng):https://www.consul.io 下載 Consul 的最新版本。

支持多種環(huán)境安裝,截圖中只顯示了部分環(huán)境。

安裝
為了讓大家學習到不同環(huán)境的安裝,單節(jié)點我們在 Windows 安裝,集群環(huán)境在 Linux 安裝。
單節(jié)點
壓縮包中就只有一個 consul.exe 的執(zhí)行文件。

cd 到對應的目錄下,使用 cmd 啟動 Consul
# -dev表示開發(fā)模式運行,另外還有 -server 表示服務模式運行
consul agent -dev -client=0.0.0.0
為了方便啟動,也可以在 consul.exe 同級目錄下創(chuàng)建一個腳本來啟動,腳本內(nèi)容如下:
consul agent -dev -client=0.0.0.0
pause
訪問管理后臺:http://localhost:8500/ 看到下圖意味著我們的 Consul 服務啟動成功了。

Consul 入門案例
點擊鏈接觀看:Consul 入門案例視頻(獲取更多請關注公眾號「哈嘍沃德先生」)
consul-demo 聚合工程。SpringBoot 2.2.4.RELEASE、Spring Cloud Hoxton.SR1。
創(chuàng)建項目
我們創(chuàng)建聚合項目來講解 Consul,首先創(chuàng)建一個 pom 父工程。



添加依賴
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 項目坐標地址 -->
<groupId>com.example</groupId>
<!-- 項目模塊名稱 -->
<artifactId>consul-demo</artifactId>
<!-- 項目版本名稱 快照版本SNAPSHOT、正式版本RELEASE -->
<version>1.0-SNAPSHOT</version>
<!-- 繼承 spring-boot-starter-parent 依賴 -->
<!-- 使用繼承方式,實現(xiàn)復用,符合繼承的都可以被使用 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
</parent>
<!--
集中定義依賴組件版本號,但不引入,
在子工程中用到聲明的依賴時,可以不加依賴的版本號,
這樣可以統(tǒng)一管理工程中用到的依賴版本
-->
<properties>
<!-- Spring Cloud Hoxton.SR1 依賴 -->
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<!-- 項目依賴管理 父項目只是聲明依賴,子項目需要寫明需要的依賴(可以省略版本信息) -->
<dependencyManagement>
<dependencies>
<!-- spring cloud 依賴 -->
<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>
</project>
服務提供者 service-provider
創(chuàng)建項目
在剛才的父工程下創(chuàng)建一個 service-provider 服務提供者的項目。





添加依賴
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>service-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 繼承父依賴 -->
<parent>
<groupId>com.example</groupId>
<artifactId>consul-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 項目依賴 -->
<dependencies>
<!-- spring cloud consul 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- spring boot actuator 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring boot web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依賴 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- spring boot test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
配置文件
application.yml
server:
port: 7070 # 端口
spring:
application:
name: service-provider # 應用名稱
# 配置 Consul 注冊中心
cloud:
consul:
# 注冊中心的訪問地址
host: localhost
port: 8500
# 服務提供者信息
discovery:
register: true # 是否需要注冊
instance-id: ${spring.application.name}-01 # 注冊實例 id(必須唯一)
service-name: ${spring.application.name} # 服務名稱
port: ${server.port} # 服務端口
prefer-ip-address: true # 是否使用 ip 地址注冊
ip-address: ${spring.cloud.client.ip-address} # 服務請求 ip
實體類
Product.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
編寫服務
ProductService.java
package com.example.service;
import com.example.pojo.Product;
import java.util.List;
/**
* 商品服務
*/
public interface ProductService {
/**
* 查詢商品列表
*
* @return
*/
List<Product> selectProductList();
}
ProductServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* 商品服務
*/
@Service
public class ProductServiceImpl implements ProductService {
/**
* 查詢商品列表
*
* @return
*/
@Override
public List<Product> selectProductList() {
return Arrays.asList(
new Product(1, "華為手機", 1, 5800D),
new Product(2, "聯(lián)想筆記本", 1, 6888D),
new Product(3, "小米平板", 5, 2020D)
);
}
}
控制層
ProductController.java
package com.example.controller;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 查詢商品列表
*
* @return
*/
@GetMapping("/list")
public List<Product> selectProductList() {
return productService.selectProductList();
}
}
該項目我們可以通過單元測試進行測試,也可以直接通過 url 使用 postman 或者瀏覽器來進行測試。
啟動類
ServiceProviderApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
訪問
訪問管理后臺:http://localhost:8500/ 看到下圖意味著我們的服務注冊至注冊中心了。


將 service-provider 項目復制一份修改端口為 7071 ,注冊實例 id 為 02。
spring:
application:
name: service-provider # 應用名稱
# 配置 Consul 注冊中心
cloud:
consul:
# 注冊中心的訪問地址
host: localhost
port: 8500
# 服務提供者信息
discovery:
register: true # 是否需要注冊
instance-id: ${spring.application.name}-02 # 注冊實例 id(必須唯一)
service-name: ${spring.application.name} # 服務名稱
port: ${server.port} # 服務端口
prefer-ip-address: true # 是否使用 ip 地址注冊
ip-address: ${spring.cloud.client.ip-address} # 服務請求 ip
# 端口
server:
port: 8602
啟動 service-provider02 結果如下:


服務消費者 service-consumer
創(chuàng)建項目
在剛才的父工程下創(chuàng)建一個 service-consumer 服務消費者的項目。





添加依賴
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>service-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 繼承父依賴 -->
<parent>
<groupId>com.example</groupId>
<artifactId>consul-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 項目依賴 -->
<dependencies>
<!-- spring cloud consul 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- spring boot actuator 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring boot web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依賴 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- spring boot test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
配置文件
application.yml
server:
port: 9090 # 端口
spring:
application:
name: service-consumer # 應用名稱
# 配置 Consul 注冊中心
cloud:
consul:
# 注冊中心的訪問地址
host: localhost
port: 8500
# 服務提供者信息
discovery:
register: false # 是否需要注冊
instance-id: ${spring.application.name}-01 # 注冊實例 id(必須唯一)
service-name: ${spring.application.name} # 服務名稱
port: ${server.port} # 服務端口
prefer-ip-address: true # 是否使用 ip 地址注冊
ip-address: ${spring.cloud.client.ip-address} # 服務請求 ip
實體類
Product.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
Order.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrice;
private List<Product> productList;
}
消費服務
OrderService.java
package com.example.service;
import com.example.pojo.Order;
public interface OrderService {
/**
* 根據(jù)主鍵查詢訂單
*
* @param id
* @return
*/
Order selectOrderById(Integer id);
}
OrderServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
/**
* 根據(jù)主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-001", "中國", 22788D,
selectProductListByLoadBalancerAnnotation());
}
private List<Product> selectProductListByLoadBalancerAnnotation() {
// ResponseEntity: 封裝了返回數(shù)據(jù)
ResponseEntity<List<Product>> response = restTemplate.exchange(
"http://service-provider/product/list",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {
});
return response.getBody();
}
}
控制層
OrderController.java
package com.example.controller;
import com.example.pojo.Order;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 根據(jù)主鍵查詢訂單
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Order selectOrderById(@PathVariable("id") Integer id) {
return orderService.selectOrderById(id);
}
}
啟動類
ServiceConsumerApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class ServiceConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
訪問
訪問:http://localhost:9090/order/1 結果如下:

下一篇我們講解 Consul 集群環(huán)境的搭建。記得關注噢~

本文采用 知識共享「署名-非商業(yè)性使用-禁止演繹 4.0 國際」許可協(xié)議。
大家可以通過 分類 查看更多關于 Spring Cloud 的文章。
?? 您的點贊和轉(zhuǎn)發(fā)是對我最大的支持。
?? 關注公眾號 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕松噢 ~