第2章
服務發現:Eureka
服務發現是微服務架構中的一個重要概念。試想當系統服務之間的依賴越來越多,A服務可能需要調用B、C、D等服務,同時被調用方也就是服務提供方可能為了保證自身高可用,還需要同時以叢集的模式部署B1、B2、C1、C2等,想象一下A的配置檔案該有多複雜。将服務提供方的位址寫死在配置檔案中,那麼服務提供方如果橫向擴充增加執行個體,是不是還需要修改作為服務調用方的A的配置檔案?這時我們就迫切需要一種服務發現的機制。所有的服務提供方啟動時向注冊中心報告自身的資訊,包括自己的位址、端口,以及提供哪些服務等相關資訊。當服務調用方需要調用服務時,隻需要問注冊中心是誰提供了相關的服務,注冊中心傳回哪些提供方提供了這些服務,調用方就可以自己根據注冊中心傳回的資訊去請求了。Eureka就提供了這樣一種能力,同時自身作為注冊中心的同時也提供了高可用的支援,支援叢集部署時各個節點之間的注冊資料同步複制。
Eureka是Netflix開源的一款提供服務注冊和發現的産品,提供了完整的服務注冊和服務發現實作,也是Spring Cloud體系中最重要、最核心的元件之一。
通俗講,Eureka就是一個服務中心,将所有可以提供的服務都注冊到它這裡來管理,其他各調用者需要的時候去注冊中心擷取,然後服務調用方再向服務提供方發起調用,避免了服務之間的直接調用,友善後續的水準擴充、故障轉移等。
是以,服務中心這麼重要的元件一旦當機将會影響全部服務,是以需要搭建Eureka叢集來保持高可用性,建議生産中最少配備兩台。随着系統流量的不斷增加,需要根據情況來擴充某個服務,Eureka内部提供均衡負載的功能,隻需要增加相應的服務端執行個體即可。那麼在系統的運作期間某個執行個體當機怎麼辦?Eureka提供心跳檢測機制,如果某個執行個體在規定的時間内沒有進行通信則會被自動剔除掉,避免了某個執行個體挂掉而影響服務。
是以,使用Eureka就自動具有了注冊中心、負載均衡、故障轉移的功能。
它主要包括兩個元件。
- Eureka Client:一個Java用戶端,用于簡化與Eureka Server的互動(通常就是微服務中的用戶端和服務端)。
- Eureka Server:提供服務注冊和發現的能力(通常就是微服務中的注冊中心)。
各個微服務啟動時,會通過Eureka Client向Eureka Server注冊自己,Eureka Server會存儲該服務的資訊,如圖2-1所示。

也就是說,每個微服務的用戶端和服務端都會注冊到Eureka Server,這就衍生出了微服務互相識别的話題。
- 同步:每個Eureka Server同時是Eureka Client(邏輯上的),多個Eureka Server之間通過複制的方式完成服務系統資料庫的同步,進而實作Eureka的高可用。
-
識别:Eureka Client會在本地緩存Eureka Server中的資訊。
即使所有Eureka Server節點都宕掉,服務消費方仍可使用本地緩存中的資訊找到服務提供方。
- 續約:微服務會周期性(預設30s)地向Eureka Server發送心跳以續約(Renew)自己的資訊(類似于heartbeat機制)。
-
續期:Eureka Server會定期(預設60s)執行一次失效服務檢測功能,它會檢查超過一定時間(預設90s)沒有續約的微服務,發現則會登出該微服務節點。
當一個注冊器用戶端通過Eureka進行注冊時,它會帶上一些描述自己情況的中繼資料,如位址、端口、健康訓示器位址、首頁等。Eureka會接收每一個服務執行個體發送的心跳包。如果心跳包超過配置的間隔時間,則這個服務執行個體就會被移除。
2.1 使用Eureka
接下來嘗試一個Eureka的示例。
建立一個Maven項目,在pom.xml中添加對Eureka服務端的依賴:
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
建立EurekaServerApplication.java,添加@EnableEurekaServer注解:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
啟動程式後,打開
http://localhost:8080/就可以看到如圖2-2所示的監控頁面,圖中展示了向Eureka注冊的所有用戶端。
2.1.1 Eureka服務提供方
啟動一個服務提供方并注冊到Eureka。建立一個項目并在pom.xml中添加依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
建立EurekaProviderApplication.java,并對外暴露一個sayHello的HTTP接口。
@EnableEurekaClient
@RestController
public class EurekaProviderApplication {
@RequestMapping("/sayHello")
public String sayHello(String name){
return "hello "+name;
}
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaProviderApplication.class).web(true).run(args);
}
在application.yml配置Eureka服務端位址以及自身服務名稱:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #在注冊中心配置Eureka位址
spring:
application:
name: myprovider #自身名字
啟動EurekaProviderApplication後,再去看Eureka的監控頁面,應該能看到如圖2-3所示資訊,表明服務已經注冊到Eureka。
還有一種方法就是直接使用原生的com.netflix.discovery.EurekaClient(對應Spring Cloud的DiscoveryClient)。通常,盡量不要直接使用原始的Netflix的Eureka Client,因為Spring已經對其進行封裝抽象,應盡可能使用DiscoveryClient。
2.1.2 Eureka服務調用方
一旦在應用中使用了@EnableDiscoveryClient或者@EnableEurekaClient,就可以從Eureka Server中使用服務發現功能。
利用如下代碼,通過DiscoveryClient可以從Eureka服務端獲得所有提供myprovider服務的執行個體清單,并根據獲得的ServiceInstance對象擷取每個提供方的相關資訊,如端口IP等,從中選取第一個提供方,通過Spring的RestTemplate發起HTTP請求進行調用。對于擷取到的提供方集合,我們根據什麼規則去選取哪個提供方?能否做到負載均衡呢?這将在第5章詳細介紹。
@EnableDiscoveryClient
@Slf4j
public class EurekaClientApplication {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Autowired
private DiscoveryClient client;
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/sayHello" ,method = RequestMethod.GET)
public String sayHello(String name) {
List<ServiceInstance> instances = client.getInstances("myprovider");
if (!instances.isEmpty()) {
ServiceInstance instance = instances.get(0);
log.info(instance.getUri().toString());
String result=restTemplate.getForObject(instance.getUri().toString()+ "/sayHello?name="+name,String.class);
return result;
}
return "failed";
}
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
此時,我們通過HTTP請求調用/sayHello接口,則可以看到服務調用方從Eureka擷取服務提供方資訊,并進行調用的日志資訊了。
不要在@PostConstruct方法以及@Scheduled中(或者任何ApplicationContext還沒初始完成的地方)使用EurekaClient。其需要等待SmartLifecycle(phase=0)初始化完成才可以。
2.2 進階場景
嘗試過Eureka的基本使用場景後,雖然功能能夠實作,但是落實到具體的企業級應用場景時,必然會有許多需要自定義的配置以及一些生産環境可能需要用到的參數。下面列出一些場景供讀者參考。
(1)Eureka的健康檢查
預設情況下,Eureka通過用戶端心跳包來檢測用戶端狀态,并不是通過spring-boot-actuator子產品的/health端點來實作的。預設的心跳實作方式可以有效地檢查Eureka用戶端程序是否正常運作,但是無法保證用戶端應用能夠正常提供服務。由于大多數微服務應用都會有一些外部資源依賴,比如資料庫、Redis緩存等,如果應用與這些外部資源無法連通時,實際上已經不能提供正常的對外服務了,但因為用戶端心跳依然在運作,是以它還是會被服務消費者調用,而這樣的調用實際上并不能獲得預期的效果。當然,我們可以開啟Eureka的健康檢查,這樣應用狀态就可以同步給Eureka了。在application.yml中添加如下配置即可:
client:
healthcheck:
enabled:true
Eureka中的執行個體一共有如下幾種狀态:UP、DOWN、STARTING、OUT_OF_SERVICE、UNKNOWN。如果需要更多的健康檢查控制,可以實作com.netflix.appinfo.Health-CheckHandler接口,根據自己的場景進行操作。
eureka.client.healthcheck.enabled=true隻能在application.yml中設定,如果在bootstrap.yml中設定,會導緻Eureka注冊為UNKNOWN的狀态。
(2)自我保護模式
Eureka在設計時,認為分布式環境的網絡是不可靠的,可能會因網絡問題導緻Eureka Server沒有收到執行個體的心跳,但是這并不能說明執行個體宕了,是以Eureka Server預設會打開保護模式,它主要是網絡分區場景下的一種保護。
一旦進入保護模式,Eureka Server将會嘗試保護其服務系統資料庫中的資訊,不再删除裡面的資料(即不會登出任何微服務)。在這種機制下,它仍然鼓勵用戶端再去嘗試調用這個所謂down狀态的執行個體,當網絡故障恢複後,該Eureka Server節點會自動退出自我保護模式。若确實調用失敗,熔斷器就派上用場了。
關于熔斷器,第6章會詳細介紹并示範。
通過修改注冊中心的配置檔案application.yml,即可打開或關閉注冊中心的自我保護模式:
server:
enable-self-presaervation: false #關閉自我保護模式(預設為打開)
綜上,自我保護模式是一種應對網絡異常的安全保護措施。它的理念是甯可同時保留所有執行個體(健康執行個體和不健康執行個體都會保留),也不盲目登出任何健康的執行個體。使用自我保護模式,可以讓Eureka叢集更加健壯、穩定。
(3)踢出當機節點
自我保護模式打開時,已關停節點是一直顯示在Eureka首頁的。
關閉自我保護模式後,由于其預設的心跳周期比較長等原因,要過一會兒才會發現已關停節點被自動踢出了。若想盡快踢出,就隻能修改預設的心跳周期參數了。
注冊中心的配置檔案application.yml需要修改的地方如下所示:
server:
enable-self-preservation: false # 關閉自我保護模式(預設為打開)
eviction-interval-timer-in-ms: 1000 # 續期時間,即掃描失效服務的間隔時間
(預設為60*1000ms)
用戶端的配置檔案application.yml需要修改的地方為:
instance:
lease-renewal-interval-in-seconds: 5 # 心跳時間,即服務續約間隔時間(預設為30s)
lease-expiration-duration-in-seconds: 15 # 發呆時間,即服務續約到期時間(預設為90s)
client:
healthcheck:
enabled: true #開啟健康檢查(依賴spring-boot-starter-actuator)
更改Eureka Server的更新頻率将打破注冊中心的自我保護功能,不建議生産環境自定義這些配置。
(4)注冊服務慢的問題
用戶端去注冊中心預設持續30s,直到執行個體自身、服務端、用戶端各自中繼資料本地緩存同步完成後服務才可用(至少需要3次心跳周期)。可以通過eureka.instance.leaseRenewalIntervalInSeconds修改這個周期,改善用戶端連結到服務的速度。不過,考慮短期的網絡波動以及服務續期等情況,在生産環境最好用預設設定。
(5)服務狀态顯示UNKNOWN
如果在Eureka監控頁面發現服務狀态顯示UNKNOWN,則很大可能是把微服務的eureka.client.healthcheck.enabled屬性配置在bootstrap.yml裡面的問題。比如,實際測試發現,Eureka首頁顯示的服務狀态,本應是UP(1),卻變成大紅色的粗體UNKNOWN(1)。
(6)自定義InstanceId
兩個相同的服務(端口不同),如果注冊時設定的都是${spring.application.name},那麼Eureka首頁隻會看到一個服務名字,而無法區分有幾個執行個體注冊上來了。于是,可以自定義生成InstanceId的規則。
Eureka服務名預設如下:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
可以在配置檔案中通過eureka.intance.intance_id來自定義:
instance:
#修改顯示的微服務名為:IP:端口
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
(7)自定義Eureka控制台服務的連結
既然微服務顯示的名稱允許修改,那麼其對應的點選連結也是可以修改的。
同樣,還是修改微服務的配置檔案,如下所示:
instance:
# ip-address: 192.168.6.66 #隻有prefer-ip-address=true時才會生效
prefer-ip-address: true #設定微服務調用位址為IP優先(預設為false)
Eureka首頁顯示的微服務調用位址,預設是
http://hostName:port/info。
而在設定prefer-ip-address=true之後,調用位址會變成
http://ip:port/info這時若再設定ip-address=192.168.6.66,則調用位址會變成
http://192.168.6.66:2100/info(8)健康度訓示器
一個Eurake執行個體的狀态頁和健康訓示器預設為/info和/health,這兩個是由Spring Boot Actuator應用提供的通路端點。可以通過以下方式進行修改:
application.yml
instance:
statusPageUrlPath: ${management.context-path}/info
healthCheckUrlPath: ${management.context-path}/health
這些位址會被用于Eureka對用戶端中繼資料的擷取,以及健康檢測。
(9)就近原則
使用者量比較大或者使用者地理位置分布範圍很廣的項目,一般都會有多個機房。這個時候如果上線服務的話,我們希望一個機房内的服務優先調用同一個機房内的服務,當同一個機房的服務不可用時,再去調用其他機房的服務,以達到減少延時的作用。Eureka有Region和Zone的概念,可以了解為現實中的大區(Region)和機房(Zone)。Eureka Client在啟動時需要指定Zone,它會優先請求自己Zone的Eureka Server擷取注冊清單。同樣,Eureka Server在啟動時也需要指定Zone。如果沒有指定,其會預設使用defaultZone。
(10)高可用配置
Eureka Server也支援運作多執行個體,并以互相注冊的方式(即夥伴機制)來實作高可用的部署,即每一台Eureka都在配置中指定另一個Eureka位址作為夥伴,它在啟動時會向夥伴節點擷取注冊清單。如此一來,Eureka叢集新加機器時,就不用擔心注冊清單的完整性。是以,隻需要在Eureka Server裡面配置其他可用的serviceUrl,就實作了注冊中心的高可用。
我們建立兩個Eureka服務端項。第1個服務端項為EurekaServer1項目中的配置檔案
/src/main/resources/application.yml。
server:
port: 8989
eureka
serviceUrl:
defaultZone:
第2個服務端項為EurekaServer2項目中的配置檔案/src/main/resources/application.yml。
port: 9898
serviceUrl:
defaultZone:
啟動Server1、Server2後,分别通路
http://127.0.0.1:8989/eureka/、
http://127.0.0.1:9898/eureka/,發現DS Replicas、General Info子產品出現了對方的資訊。讀者可以自行測試,分别單獨向Server1或者Server2進行服務注冊時,都會自動同步給另外一個注冊中心。
在生産環境中大于兩台注冊中心的條件下,可以同理将其配置成如圖2-4所示的雙向環形。
2.3 小結
本章詳細講解了Eureka元件的工作原理,并結合示例介紹了Eureka的服務提供方和服務調用方的使用步驟。在進階場景章節中我們深入探究了各個場景下使用Eureka的各種解決方案與自定義場景的配置。下個章開始學習如何使用Config元件來對Eureka以及其他元件進行動态化配置。