Netflix Eureka 2.X https://github.com/Netflix/eureka/wiki 官方宣告中止開發,但其實對國內的用戶影響甚小,一方面國內大都使用的是 Eureka 1.X 系列,而且官方也在積極維護 1.X https://github.com/Netflix/eureka/releases。java
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.git
Eureka 1.x is a core part of Netflix's service discovery system and is still an active project.github
翻譯:web
有關 eureka 2.0 的現有開源工做已中止。在 2.x 分支上做爲現有工做資料庫的一部分發布的代碼庫和工件被視爲使用後果自負。算法
Eureka 1.x 是 Netflix 服務發現系統的核心部分,仍然是一個活躍的項目。spring
雖然 Eureka,Hystrix 等再也不繼續開發或維護,可是目前來講不影響使用,無論怎麼說感謝開源,向 Netflix 公司的開源致敬。shell
另外一方面 Spring Cloud 支持不少服務發現的軟件,Eureka 只是其中之一,好比咱們今天要講的主角 Consul
。下面是 Spring Cloud 支持的服務發現軟件以及特性對比。apache
常見的註冊中心
- 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 |
雪崩保護 | 有 | 有 | 無 | 無 |
自動註銷實例 | 支持 | 支持 | 不支持 | 支持 |
訪問協議 | HTTP | HTTP/DNS | HTTP/DNS | TCP |
監聽支持 | 支持 | 支持 | 支持 | 支持 |
多數據中心 | 支持 | 支持 | 支持 | 不支持 |
跨註冊中心同步 | 不支持 | 支持 | 支持 | 不支持 |
SpringCloud集成 | 支持 | 支持 | 支持 | 支持 |
Consul 介紹
Consul 是 HashiCorp 公司推出的開源工具,用於實現分佈式系統的服務發現與配置。與其它分佈式服務註冊與發現的方案,Consul 的方案更「一站式」,內置了服務註冊與發現框架、分佈一致性協議實現、健康檢查、Key/Value 存儲(配置中心)、多數據中心方案,再也不須要依賴其它工具(好比 ZooKeeper 等),使用起來也較爲簡單。瀏覽器
Consul 使用 Go 語言編寫,所以具備自然可移植性(支持Linux、Windows 和 Mac OS);安裝包僅包含一個可執行文件,方便部署,與 Docker 等輕量級容器可無縫配合。緩存
Consul 特性
-
Raft 算法
-
服務發現
-
健康檢查
-
Key/Value 存儲(配置中心)
-
多數據中心
-
支持 http 和 dns 協議接口
-
官方提供 web 管理界面
Consul 角色
點擊連接觀看:Consul 角色視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
- client:客戶端,無狀態,將 HTTP 和 DNS 接口請求轉發給局域網內的服務端集羣。
- server:服務端,保存配置信息,高可用集羣,每一個數據中心的 server 數量推薦爲 3 個或者 5 個。
首先,圖中有兩個數據中心,分別爲 Datacenter1 和 Datacenter2 。Consul 很是好的支持多個數據中心,每一個數據中心內,有客戶端和服務器端,服務器通常爲 3~5 個,這樣能夠在穩定和性能上達到平衡,由於更多的機器會使數據同步很慢。不過客戶端是沒有限制的,能夠有成千上萬個。
數據中心內的全部節點都會加入到 Gossip (流言)協議。這就意味着有一個 Gossip 池,其中包含這個數據中心全部的節點。客戶端不須要去配置服務器地址信息,發現服務工做會自動完成。檢測故障節點的工做不是放在服務器端,而是分佈式的;這使得失敗檢測相對於本地化的心跳機制而言,更具可拓展性。在選擇 leader 這種重要的事情發生的時候,數據中心被用做消息層來作消息廣播。
每一個數據中心內的服務器都是單個 Raft 中節點集的一部分。這意味着他們一塊兒工做,選擇一個單一的領導者——一個具備額外職責的選定的服務器。leader 負責處理全部查詢和事物。事物也必須做爲同步協議的一部分複製到節點集中的全部節點。因爲這個要求,當非 leader 服務器接收到 RPC 請求時,就會將請求其轉發給集羣 leader。
服務器端節點同時也做爲 WAN Gossip 池的一部分,WAN 池和 LAN 池不一樣的是,它針對網絡高延遲作了優化,並且只包含其餘Consul 服務器的節點。這個池的目的是容許數據中心以最少的消耗方式發現對方。啓動新的數據中心與加入現有的 WAN Gossip 同樣簡單。由於這些服務器都在這個池中運行,它還支持跨數據中心請求。當服務器收到對不一樣數據中心的請求時,它會將其轉發到正確數據中心中的隨機服務器。那個服務器可能會轉發給本地的 leader。
這樣會使數據中心的耦合很是低。可是因爲故障檢測,鏈接緩存和複用,跨數據中心請求相對快速可靠。
總的來講,數據不會在不一樣的數據中心之間作複製備份。當收到一個請求處於別的數據中心的資源時,本地的 Consul 服務器會發一個 RPC 請求到遠端的 Consul 服務器,而後返回結果。若是遠端數據中心處於不可用狀態,那麼這麼資源也會不可用,但這不影響本地的數據中心。在一些特殊的狀況下,有限的數據集會被跨數據中心複製備份,好比說 Consul 內置的 ACL 複製能力,或者像 consul-replicate 這樣的外部工具。
Consul 工做原理
服務發現以及註冊
當服務 Producer 啓動時,會將本身的 Ip/host 等信息經過發送請求告知 Consul,Consul 接收到 Producer 的註冊信息後,每隔 10s(默認)會向 Producer 發送一個健康檢查的請求,檢驗 Producer 是否健康。
服務調用
當 Consumer 請求 Product 時,會先從 Consul 中拿到存儲 Product 服務的 IP 和 Port 的臨時表(temp table),從temp table 表中任選一個· Producer 的 IP 和 Port, 而後根據這個 IP 和 Port,發送訪問請求;temp table 表只包含經過了健康檢查的 Producer 信息,而且每隔 10s(默認)更新。
Consul 安裝
Eureka 其實就是個 Servlet 程序,跑在 Servlet 容器中;Consul 則是用 go 語言編寫的第三方工具須要單獨安裝使用。
下載
訪問 Consul 官網:https://www.consul.io 下載 Consul 的最新版本。
支持多種環境安裝,截圖中只顯示了部分環境。
安裝
爲了讓你們學習到不一樣環境的安裝,單節點咱們在 Windows 安裝,集羣環境在 Linux 安裝。
單節點
壓縮包中就只有一個 consul.exe
的執行文件。
cd 到對應的目錄下,使用 cmd 啓動 Consul
# -dev表示開發模式運行,另外還有 -server 表示服務模式運行 consul agent -dev -client=0.0.0.0
爲了方便啓動,也能夠在 consul.exe 同級目錄下建立一個腳原本啓動,腳本內容以下:
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
。
建立項目
咱們建立聚合項目來說解 Consul,首先建立一個 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 依賴 --> <!-- 使用繼承方式,實現複用,符合繼承的均可以被使用 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.4.RELEASE</version> </parent> <!-- 集中定義依賴組件版本號,但不引入, 在子工程中用到聲明的依賴時,能夠不加依賴的版本號, 這樣能夠統一管理工程中用到的依賴版本 --> <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
建立項目
在剛纔的父工程下建立一個 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, "聯想筆記本", 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
建立項目
在剛纔的父工程下建立一個 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 { /** * 根據主鍵查詢訂單 * * @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; /** * 根據主鍵查詢訂單 * * @param id * @return */ @Override public Order selectOrderById(Integer id) { return new Order(id, "order-001", "中國", 22788D, selectProductListByLoadBalancerAnnotation()); } private List<Product> selectProductListByLoadBalancerAnnotation() { // ResponseEntity: 封裝了返回數據 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; /** * 根據主鍵查詢訂單 * * @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 集羣環境的搭建。記得關注噢~
本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議
。
你們能夠經過 分類
查看更多關於 Spring Cloud
的文章。
🤗 您的點贊
和轉發
是對我最大的支持。
📢 掃碼關注 哈嘍沃德先生
「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~