注冊中心概述
什么是注冊中心?
相當於服務之間的‘通訊錄’,記錄了服務和服務地址之間的映射關系。在分布式架構中服務會注冊到這里。當服務需要調用其他服務時,就在注冊中心找到其他服務的地址,進行調用
注冊中心的主要作用?
注冊中心一般有以下的功能:
- 服務發現
- 服務注冊/反注冊:保存服務提供者和調用者的關系
- 服務訂閱/取消訂閱:服務調用者訂閱服務提供者的信息
- 服務路由:篩選整合服務提供者
- 服務配置
- 配置訂閱:服務提供者和消費者訂閱微服務相關的配置
- 配置下發:主動將配置推送給提供者和消費者
- 服務健康檢測
- 檢測服務提供者的健康情況
Eureka
Eureka是SpringCloud微服務架構中常用的注冊中心,其架構圖如下:
由其架構圖可以看出,Eureka可以分為三部分:Eureka服務端,Eureka服務提供者,Eureka服務消費者。
其中Eureka服務端需要作為獨立的服務運行,而服務提供者、消費者則是需要使用EurekaClient嵌入我們自己的服務中。
其運行原理是這樣的:
- 當服務提供者啟動時,會向注冊中心發送請求,在注冊中心注冊實例。並且每隔一段時間向注冊中心發送心跳,注冊中心會保存實例和地址的映射關系
- 當服務消費者啟動時,會從注冊中心拉去所有的注冊信息,並緩存起來。當需要調用某一個服務時。根據緩存的注冊信息直接調用服務
聰明的你這個時候會察覺到。既然消費者使用的是緩存的注冊信息。那么一定會存在一種情況就是服務提供者這邊已經宕機。這個時候消費者根據緩存的信息沒有及時更新就會導致調用失敗,Eureka是怎么解決這個問題的呢?且往下看。
服務端的搭建
- 創建工程,導入坐標
這里推薦使用 Spring Initializer 直接創建。選擇 web 和 euraka 即可。主要引入的依賴如下:
<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-server</artifactId>
</dependency>
- 配置 application.yml ,說明見注釋
server:
port: 9000
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false # 是否將自己注冊到注冊中心
fetch-registry: false # 是否從Eureka中獲取注冊信息
service-url: # Eureka Client 的請求地址
defaultZone: http://#{eureka.instance.hostname}:#{server.port}/eureka/
- 配置啟動類
啟動類除了常規的 @SpringBootApplication 外,還要添加 @EnableEurekaServer 表示開啟 Eureka 服務
- 啟動項目,瀏覽器訪問 localhost:9000 出現以下頁面即表示服務搭建成功
注冊服務到Eureka
- 在要注冊的工程中導入Eureka Client的坐標
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在 application.yml 中配置Eureka服務端的地址:
這里需要特別注意:Eureka客戶端配置的服務端地址使用的key是 defaultZone,不是 default_zone 如果寫錯會導致客戶端無法注冊服務,進而導致客戶端無法啟動
server:
port: 9001
spring:
application:
name: SERVICE_PROVIDER # 服務名稱
datasource:
url: jdbc:mysql://192.168.25.128:3306/mysql?characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT
driver-class-name: com.mysql.cj.jdbc.Driver
password: 521
username: keats
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 剛剛在Eureka Server 配置的請求地址
register-with-eureka: true # 注冊到注冊中心
fetch-registry: false # 作為服務提供者,可以不用從Eureka獲取注冊信息。視實際情況而定
instance:
prefer-ip-address: true # 使用IP地址注冊
- 在啟動類加上 @EnableEurekaClient 注解(SpringCloud Finchley.RELEASE 版本及之后的版本會在項目中引入EurekaClient依賴后自動開啟,我們使用的是最新版。因此也可以不用添加)
Euraka 客戶端默認每隔 30 S向服務端發送一次心跳請求,如果服務端 90 S沒有收到某客戶端發送的請求將視為客戶端宕機。會將其從服務列表剔除
消費者獲取和使用服務
搭建服務消費者和提供者的步驟類似,首先是添加 eureka-client 依賴
之后配置 application.yml 如下:
server:
port: 9002
spring:
application:
name: SERVICE_CONSUMER # 服務名稱
eureka:
client:
service-url: # 剛剛在Eureka Server 配置的請求地址
defaultZone: http://localhost:9000/eureka/
fetch-registry: true
register-with-eureka: false
這樣該服務就具備了從Eureka獲取服務的能力,那具體怎么使用呢?
Spring 為我們提供了一個Bean:DiscoveryClient (注意需要導入:org.springframework.cloud.client.discovery 包下的DiscoveryClient 而不是 netflix 包下的類)
我們在需要獲取服務的類里面注入該類
@Autowired
private DiscoveryClient discoveryClient;
接着調用其 getInstances(String instancdName) 方法,通過服務的名稱獲取服務列表。我們這里只注冊了一個服務提供方沒有搭集群所以直接使用列表第0位的服務實體。而實體提供了 getUri() 方法用於獲取服務提供者的 url。接着我們用該方法替換硬編碼的 url 即可完成 Eureka 的使用。核心代碼如下:
網上的很多其他教程在這里使用的都是 getHost() + ":" + getPort() 拼接,推測可能是版本比較老舊。新API既然已經提供了 getUri() 方法我們就要積極使用。這里建議讀者們在使用某Bean的方法時通過打點的方式閱讀一下其開放的API,大概了解一下
@GetMapping("teacher/users")
public List<User> getAllUser(){
List<ServiceInstance> service_provider = discoveryClient.getInstances("SERVICE_PROVIDER");
ServiceInstance serviceInstance = service_provider.get(0);
return restTemplate.getForObject(serviceInstance.getUri() + "/api/v1/users", List.class);
}
Eureka的自我保護
如上圖提示,表示Eureka進入了自我保護模式。自我保護模式的介紹如下:
Eureka Server 在運行期間會去統計心跳失敗比例在 15 分鍾之內是否高於 85%,如果高於 85%,Eureka Server 會將這些實例保護起來,讓這些實例不會過期。在我看來可以用一句古成語來形容這種模式---“三人成虎” 即當越來越多的服務提供者心跳不能到達時。Eureka開始不在懷疑是提供者GG,而懷疑自己了!
在開發環境中,我們往往啟動一個Eureka服務 + 一個 Eureka 提供者,如果此時提供者正好出了問題。90S未發送心跳。但由於滿足自我保護條件(這段時間失敗比例為100%),Eureka不會將服務剔除。會直接導致服務消費者無法正確獲取服務。因此開發環境中建議關閉其自我保護機制,而在生產環境打開之, yml 配置如下:
eureka:
server:
enable-self-preservation: false # 關閉自我保護
Eureka集群
我給項目搭建了Eureka注冊中心,和配套的服務提供方與服務消費方。完成了服務的靈活調用。從此我們可以將項目中的各個模塊都拆分成微服務,注冊到注冊中心相互調用。但此做法在帶來便利性的同時,也為我們帶來的隱患:如果Eureka宕掉,怎么辦?
解決辦法就是搭建Eureka集群,一個Eureka倒下了,還有另一個Eureka兄弟頂着。那如何搭建Eureka集群呢?
這個問題Spring已經幫我們解決了。通過 Eureka 服務端之間相互注冊的方式,Eureka 服務端就會互相同步注冊信息,好兄弟嘛,你的就是我的,我的也是你的。
我在項目中用 SpringProfile 來模擬 Eureka 集群的運行,具體的操作方法見SpringProfile輕松切換多環境配置文件:
- 首先在修改兩個 yml 文件的端口,避免端口沖突
- 打開 Eureka 服務將自己注冊到服務的開關、打開 Eureka 服務從服務獲取注冊信息的開關
- 配置對方的服務地址
- 啟動兩個 Eureka 服務端
這樣就完成了兩個Eureka服務端的搭建。此時有任何一個服務端收到服務注冊,都會同步到另一個。所以理論上我們可以將服務端注冊到其中任何一個,但是為了保險起見。我們還是將服務注冊到每個Eureka上。實現方法就是將 defaultZone 的值填寫多個Eureka的地址,用逗號隔開
其他優化項目
在控制台顯示服務的IP
eureka:
instance:
prefer-ip-address: true # 使用IP地址注冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # 自定義注冊ID為ip:端口的格式
修改服務剔除時間
默認情況下,服務提供者每隔30S會發送一次心跳到Eureka服務端,且在90S內未接收心跳才會剔除服務。開發階段來說這個優點長了。可通過以下配置修改
eureka:
instance:
prefer-ip-address: true # 使用IP地址注冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # 自定義注冊ID為ip:端口的格式
lease-renewal-interval-in-seconds: 5 #發送心跳的間隔
lease-expiration-duration-in-seconds: 10 #續約到期時間