1. 背景
1.1 問題描述
我們如果通過 RestTamplate 進行遠程調用時,URL 是寫死的,例如:
String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
當機器更換或者新增機器時,這個 URL 就需要相應地變更。這就意味著要通知所有相關服務去修改,進而導致各個項目的配置文件反復更新,各個項目頻繁部署。這種工作既繁瑣又沒有太多實際價值,卻又不得不做,給開發者帶來極大的困擾。
1.2 解決思路
我們可以從生活場景中獲取靈感。在生活里,我們難免要與各種機構(如醫院、學校、政府部門等)打交道,需要保存它們的電話號碼。若機構更換電話號碼,就需通知所有使用方,但這些機構的使用方群體龐大,根本無法做到一一通知,該怎么辦呢?
通常的做法是,機構電話變更時通知 114。用戶需要聯系機構時,先撥打 114 查詢電話,再聯系相應機構。114 查號臺主要有兩個作用:
- 號碼注冊:服務方將電話上報給 114。
- 號碼查詢:使用方通過 114 能查到對應的號碼。
同樣,在微服務開發中,也可采用類似方案。服務啟動或變更時,向注冊中心進行報道,注冊中心記錄應用和 IP 的關系。調用方在調用時,先去注冊中心獲取服務方的 IP,再去服務方進行調用。
1.3 什么是注冊中心
在早期的架構體系中,集群概念尚未普及,機器數量較少,那時直接使用 DNS + Nginx 就能滿足幾乎所有服務的發現需求,相關注冊信息直接配置在 Nginx 中。然而,隨著微服務的興起和流量的急劇增長,機器規模不斷擴大,且機器上下線行為頻繁。此時,依靠運維人員手動維護這些配置信息變得非常麻煩。于是,開發者們期望有一個工具,它能維護一個服務列表,機器上線、宕機等信息都能自動更新到這個服務列表上,客戶端獲取這個列表后可直接進行服務調用,這就是注冊中心。
注冊中心主要有三種角色:
- 服務提供者(Server):在一次業務中,被其他微服務調用的服務,即提供接口給其他微服務。
- 服務消費者(Client):在一次業務中,調用其他微服務的服務,即調用其他微服務提供的接口。
- 服務注冊中心(Registry):用于保存 Server 的注冊信息,當 Server 節點發生變更時,Registry 會同步變更。服務與注冊中心通過一定機制通信,如果注冊中心與某服務長時間無法通信,就會注銷該實例。
它們之間的關系以及工作內容,可以通過兩個概念來描述:
- 服務注冊:服務提供者在啟動時,向 Registry 注冊自身服務,并向 Registry 定期發送心跳匯報存活狀態。
- 服務發現:服務消費者從注冊中心查詢服務提供者的地址,并通過該地址調用服務提供者的接口。服務發現的一個重要作用就是為服務消費者提供一個可用的服務列表。
1.4 CAP 理論
談及注冊中心,就無法避開 CAP 理論。CAP 理論是分布式系統設計中最基礎且最為關鍵的理論。
- 一致性(Consistency):CAP 理論中的一致性指的是強一致性,即所有節點在同一時間具有相同的數據。
- 可用性(Availability):保證每個請求都有響應(響應結果可能不對)。
- 分區容錯性(Partition Tolerance):當出現網絡分區后,系統仍然能夠對外提供服務。
舉例來說,一個部門在全國各地都有崗位,總部下發通知,由于通知需開會周知全員,當有客戶咨詢時:
- 一致性:所有成員對客戶的回應結果都是一致的。
- 可用性:客戶咨詢時,一定有回應。
- 分區容錯性:當其中一個成員休假時,這個部門的其他成員也可以對客戶提供咨詢服務。
CAP 理論表明:一個分布式系統不可能同時滿足數據一致性、服務可用性和分區容錯性這三個基本需求,最多只能同時滿足其中的兩個。
在分布式系統中,系統間的網絡無法 100% 保證健康,服務又必須對外提供服務,因此 Partition Tolerance 不可避免。那就只能在 C 和 A 中選擇一個,即 CP 或者 AP 架構。
- CP 架構:為保證分布式系統對外的數據一致性,選擇不返回任何數據。
- AP 架構:為保證分布式系統的可用性,節點 2 返回 V0 版本的數據(即使這個數據不正確)。
更多參考:一文看懂|分布式系統之CAP理論-騰訊云開發者社區-騰訊云
1.5 常見的注冊中心
- Zookeeper:Zookeeper 的官方并未明確表明它是一個注冊中心,但在國內 Java 體系中,大部分集群環境都依賴 Zookeeper 來實現注冊中心的功能。
- Eureka:Eureka 是 Netflix 開發的基于 REST 的服務發現框架,主要用于服務注冊、管理、負載均衡和服務故障轉移。官方聲明在 Eureka 2.0 版本停止維護,不建議使用。不過,Eureka 是 SpringCloud 服務注冊 / 發現的默認實現,所以目前仍有許多公司在使用。
- Nacos:Nacos 是 Spring Cloud Alibaba 架構中重要的組件,除具備服務注冊、服務發現功能外,Nacos 還支持配置管理、流量管理、DNS、動態 DNS 等多種特性。
下面通過表格對比一下這三種注冊中心基于 CAP 理論的特點:
注冊中心 | CAP 理論 |
Zookeeper | CP |
Eureka | AP |
Nacos | CP 或 AP(默認 AP) |
在分布式環境中,拿到一個錯誤的數據,往往比無法提供實例信息導致請求失敗要好(例如淘寶 11.11、京東 618 等活動,都遵循 AP 原則)。在我們的課堂中,會為大家介紹 Eureka 和 Nacos 的使用。
2. Eureka 介紹
Eureka 是 Netflix OSS 套件中關于服務注冊和發現的解決方案。Spring Cloud 對 Eureka 進行了集成,并作為優先推薦方案進行宣傳。盡管目前 Eureka 2.0 已停止維護,在新的微服務架構設計中也不再建議使用,但當前仍有大量公司的微服務系統將 Eureka 作為注冊中心。
官方文檔:Home · Netflix/eureka Wiki · GitHub
Eureka 主要分為兩個部分:
- Eureka Server:作為注冊中心 Server 端,向微服務應用程序提供服務注冊、發現、健康檢查等能力。
- Eureka Client:服務提供者,服務啟動時,會向 Eureka Server 注冊自己的信息(IP、端口、服務信息等),Eureka Server 會存儲這些信息。
關于 Eureka 的學習,主要涵蓋以下三個部分:
- ?搭建 Eureka Server。
- 將 order - service、product - service 都注冊到 Eureka。
- order - service 遠程調用時,從 Eureka 中獲取 product - service 的服務列表,然后進行交互。?
3. 搭建 Eureka Server
Eureka - server 是一個獨立的微服務。
3.1 創建 Eureka - server 子模塊
此步驟可通過相應的開發工具(如 IDEA)進行操作,創建一個新的 Maven 子模塊,用于搭建 Eureka Server。
3.2 引入 eureka - server 依賴
在創建好的子模塊的 pom.xml 文件中添加如下依賴:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring - cloud - starter - netflix - eureka - server</artifactId></dependency>
3.3 項目構建插件
同樣在 pom.xml 文件中,配置項目構建插件:
3.4 完善啟動類
為項目編寫一個啟動類,并在啟動類上添加 @EnableEurekaServer 注解,開啟 eureka 注冊中心服務。示例代碼如下:
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}}
3.5 編寫配置文件
在 application.yml 文件中進行如下配置:
server:port: 10010spring:application:name: eureka - servereureka:instance:hostname: localhostclient:fetch - registry: false
# 表示是否從Eureka Server獲取注冊信息,默認為true。因為這是一個單點的Eureka Server,不需要同步其他的Eureka Server節點的數據,這里設置為false
register - with - eureka: false # 表示是否將自己注冊到Eureka Server,默認為true。由于當前應用就是Eureka Server,故而設置為false
service - url:
# 設置與Eureka Server的地址,查詢服務和注冊服務都需要依賴這個地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3.6 啟動服務
啟動服務后,訪問注冊中心:http://127.0.0.1:10010/ ,若能看到相應頁面,則表明 eureka - server 已啟動成功。
4. 服務注冊
接下來我們將 product - service 注冊到 eureka - server 中。
4.1 引入 eureka - client 依賴
在 product - service 模塊的 pom.xml 文件中添加如下依賴:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring - cloud - starter - netflix - eureka - client</artifactId></dependency>
4.2 完善配置文件
在 product - service 模塊的 application.yml 文件中添加服務名稱和 eureka 地址:
spring:application:name: product - serviceeureka:client:service - url:defaultZone: http://127.0.0.1:10010/eureka
4.3 啟動服務
啟動 product - service 服務后,刷新注冊中心:http://127.0.0.1:10010/ ,可看到 product - service 已成功注冊到 eureka 上。
5. 服務發現
接下來我們修改 order - service,在遠程調用時,從 eureka - server 拉取 product - service 的服務信息,實現服務發現。
5.1 引入依賴
服務注冊和服務發現都封裝在 eureka - client 依賴中,所以服務發現時,同樣引入 eureka - client 依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring - cloud - starter - netflix - eureka - client</artifactId>
</dependency>
5.2 完善配置文件
服務發現也需要知道 eureka 地址,因此配置內容與服務注冊一致,都是配置 eureka 信息:
spring:
application:
name: order - service
eureka:
client:
service - url:
defaultZone: http://127.0.0.1:10010/eureka
5.3 遠程調用
在遠程調用時,我們需要從 eureka - server 中獲取 product - service 的列表(可能存在多個服務),并選擇其中一個進行調用。示例代碼如下:
import com.bite.order.mapper.OrderMapper;import com.bite.order.model.OrderInfo;import com.bite.order.model.ProductInfo;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import jakarta.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.List;import org.slf4j.Logger;import org.slf4j.LoggerFactory;@Servicepublic class OrderService {private static final Logger log = LoggerFactory.getLogger(OrderService.class);@Autowiredprivate OrderMapper orderMapper;@Resourceprivate DiscoveryClient discoveryClient;@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();//根據應用名稱獲取服務列表List<ServiceInstance> instances = discoveryClient.getInstances("product - service");//服務可能有多個,獲取第一個EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);log.info(instance.getInstanceId());//拼接urlString url = instance.getUri() + "/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}}
5.4 啟動服務
啟動 order - service 服務后,刷新注冊中心:http://127.0.0.1:10010/ ,可看到 order - service 已注冊到 eureka 上。訪問接口:http://127.0.0.1:8080/order/1 ,可以發現遠程調用也成功了。
6. Eureka 和 Zookeeper 區別
Eureka 和 Zookeeper 都是用于服務注冊和發現的工具,它們的區別如下:
對比項 | Eureka | Zookeeper |
開源項目所屬 | Netflix 開源的項目 | Apache 開源的項目 |
遵循原則 | 基于 AP 原則,保證高可用 | 基于 CP 原則,保證數據一致性 |
節點關系 | 每個節點都是均等的 | 節點區分 Leader 和 Follower 或 Observer |
選舉機制 | 無選舉機制 | 如果 Zookeeper 的 Leader 發生故障時,需要重新選舉,選舉過程集群會有短暫時間的不可用 |
通過以上對 Eureka 的詳細介紹,相信大家對服務注冊與發現以及 Eureka 在 Spring Cloud 中的應用有了更深入的理解。在實際項目中,可根據具體需求選擇合適的注冊中心。