2016年 06月 25日node
因爲項目中使用了Eureka作服務註冊和發現,因此最近花了一些時間比較深刻的研究了一下Eureka,今天就來介紹一下它的實現細節。git
其官方文檔中對本身的定義是:github
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. We call this service, the Eureka Server. Eureka also comes with a Java-based client component,the Eureka Client, which makes interactions with the service much easier. The client also has a built-in load balancer that does basic round-robin load balancing.spring
簡單來講Eureka就是Netflix開源的一款提供服務註冊和發現的產品,而且提供了相應的Java客戶端。數據庫
那麼爲何咱們在項目中使用了Eureka呢?我大體總結了一下,有如下幾方面的緣由:緩存
相信你們看到這裏,已經對Eureka有了一個初步的認識,接下來咱們就來深刻了解下它吧~架構
上圖簡要描述了Eureka的基本架構,由3個角色組成:app
須要注意的是,上圖中的3個角色都是邏輯角色。在實際運行中,這幾個角色甚至能夠是同一個實例,好比在咱們項目中,Eureka Server和Service Provider就是同一個JVM進程。ide
上圖更進一步的展現了3個角色之間的交互。性能
爲了給你們一個更直觀的印象,咱們能夠經過一個簡單的demo來實際運行一下,從而對Eureka有更好的瞭解。
Git倉庫:git@github.com:nobodyiam/spring-cloud-in-action.git
這個項目使用了Spring Cloud相關類庫,包括:
Demo項目使用了Spring Cloud Config作配置,因此第一步先在本地啓動Config Server。
因爲項目基於Spring Boot開發,因此直接運行com.nobodyiam.spring.cloud.in.action.config.ConfigServerApplication
便可。
Eureka Server的Demo模塊名是:eureka-server
。
eureka-server
是一個基於Spring Boot的Web應用,咱們首先須要作的就是在pom中引入Spring Cloud Eureka Server的依賴。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.1.0.RC2</version> </dependency>
啓用Eureka Server很是簡單,只須要加上@EnableEurekaServer
便可。
@EnableEurekaServer @SpringBootApplication public class EurekaServiceApplication { public static void main(String[] args) { SpringApplication.run(EurekaServiceApplication.class, args); } }
作完以上配置後,啓動應用,Eureka Server就開始工做了!
啓動完,打開http://localhost:8761就能看到啓動成功的畫面了。
Service Provider的Demo模塊名是:reservation-service
。
Service Consumer的Demo模塊名是:reservation-client
。
reservation-service
和reservation-client
都是基於Spring Boot的Web應用,咱們首先須要作的就是在pom中引入Spring Cloud Eureka的依賴。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.1.0.RC2</version> </dependency>
啓用Service Provider很是簡單,只須要加上@EnableDiscoveryClient
便可。
@EnableDiscoveryClient @SpringBootApplication public class ReservationServiceApplication { public static void main(String[] args) { new SpringApplicationBuilder(ReservationServiceApplication.class) .run(args); } }
作完以上配置後,啓動應用,Server Provider就開始工做了!
啓動完,打開http://localhost:8761就能看到服務已經註冊到Eureka Server了。
啓動Service Consumer其實和Service Provider同樣,由於本質上Eureka提供的客戶端是不區分Provider和Consumer的,通常狀況下,Provider同時也會是Consumer。
@EnableDiscoveryClient @SpringBootApplication public class ReservationClientApplication { @Bean CommandLineRunner runner(DiscoveryClient dc) { return args -> { dc.getInstances("reservation-service") .forEach(si -> System.out.println(String.format( "Found %s %s:%s", si.getServiceId(), si.getHost(), si.getPort()))); }; } public static void main(String[] args) { SpringApplication.run(ReservationClientApplication.class, args); } }
上述代碼中經過dc.getInstances("reservation-service")
就能獲取到當前全部註冊的reservation-service
服務。
看了前面的demo,咱們已經初步領略到了Spring Cloud和Eureka的強大之處,經過短短几行配置就實現了服務註冊和發現!
相信你們必定想了解Eureka是如何實現的吧,因此接下來咱們繼續Dive!首先來看下Eureka Server的幾個對外接口實現。
首先來看Register(服務註冊),這個接口會在Service Provider啓動時被調用來實現服務註冊。同時,當Service Provider的服務狀態發生變化時(如自身檢測認爲Down的時候),也會調用來更新服務狀態。
接口實現比較簡單,以下圖所示。
ApplicationResource
類接收Http服務請求,調用PeerAwareInstanceRegistryImpl
的register
方法PeerAwareInstanceRegistryImpl
完成服務註冊後,調用replicateToPeers
向其它Eureka Server節點(Peer)作狀態同步註冊的服務列表保存在一個嵌套的hash map中:
以3.2.4.2中的截圖爲例,RESERVATION-SERVICE
就是app name,jason-mbp.lan:reservation-service:8000
就是instance name。
Hash map定義以下:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
Renew(服務續約)操做由Service Provider按期調用,相似於heartbeat。主要是用來告訴Eureka Server Service Provider還活着,避免服務被剔除掉。接口實現以下圖所示。
能夠看到,接口實現方式和register
基本一致:首先更新自身狀態,再同步到其它Peer。
Cancel(服務下線)通常在Service Provider shut down的時候調用,用來把自身的服務從Eureka Server中刪除,以防客戶端調用不存在的服務。接口實現以下圖所示。
Fetch Registries由Service Consumer調用,用來獲取Eureka Server上註冊的服務。
爲了提升性能,服務列表在Eureka Server會緩存一份,同時每30秒更新一次。
Eviction(失效服務剔除)用來按期在Eureka Server檢測失效的服務,檢測標準就是超過必定時間沒有Renew的服務。
默認失效時間爲90秒,也就是若是有服務超過90秒沒有向Eureka Server發起Renew請求的話,就會被當作失效服務剔除掉。
失效時間能夠經過eureka.instance.leaseExpirationDurationInSeconds
進行配置,按期掃描時間能夠經過eureka.server.evictionIntervalTimerInMs
進行配置。
接口實現邏輯見下圖:
在前面的Register、Renew、Cancel接口實現中,咱們看到了都會有replicateToPeers操做,這個就是用來作Peer之間的狀態同步。
經過這種方式,Service Provider只須要通知到任意一個Eureka Server後就能保證狀態會在全部的Eureka Server中獲得更新。
具體實現方式其實很簡單,就是接收到Service Provider請求的Eureka Server,把請求再次轉發到其它的Eureka Server,調用一樣的接口,傳入一樣的參數,除了會在header中標記isReplication=true,從而避免重複的replicate。
那你們可能會有疑問,Eureka Server是怎麼知道有多少Peer的呢?
Eureka Server在啓動後會調用EurekaClientConfig.getEurekaServerServiceUrls
來獲取全部的Peer節點,而且會按期更新。按期更新頻率能夠經過eureka.server.peerEurekaNodesUpdateIntervalMs
配置。
這個方法的默認實現是從配置文件讀取,因此若是Eureka Server節點相對固定的話,能夠經過在配置文件中配置來實現。
若是但願能更靈活的控制Eureka Server節點,好比動態擴容/縮容,那麼能夠override getEurekaServerServiceUrls
方法,提供本身的實現,好比咱們的項目中會經過數據庫讀取Eureka Server列表。
具體實現以下圖所示:
最後再來看一下一個新的Eureka Server節點加進來,或者Eureka Server重啓後,如何來作初始化,從而可以正常提供服務。
具體實現以下圖所示,簡而言之就是啓動時把本身當作是Service Consumer從其它Peer Eureka獲取全部服務的註冊信息。而後對每一個服務,在本身這裏執行Register,isReplication=true,從而完成初始化。
如今來看下Service Provider的實現細節,主要就是Register、Renew、Cancel這3個操做。
Service Provider要對外提供服務,一個很重要的步驟就是把本身註冊到Eureka Server上。
這部分的實現比較簡單,只須要在啓動時和實例狀態變化時調用Eureka Server的接口註冊便可。須要注意的是,須要確保配置eureka.client.registerWithEureka
=true。
Renew操做會在Service Provider端按期發起,用來通知Eureka Server本身還活着。 這裏有兩個比較重要的配置須要注意一下:
eureka.instance.leaseRenewalIntervalInSeconds
Renew頻率。默認是30秒,也就是每30秒會向Eureka Server發起Renew操做。
eureka.instance.leaseExpirationDurationInSeconds
服務失效時間。默認是90秒,也就是若是Eureka Server在90秒內沒有接收到來自Service Provider的Renew操做,就會把Service Provider剔除。
具體實現以下:
在Service Provider服務shut down的時候,須要及時通知Eureka Server把本身剔除,從而避免客戶端調用已經下線的服務。
邏輯自己比較簡單,經過對方法標記@PreDestroy
,從而在服務shut down的時候會被觸發。
這裏你們疑問又來了,Service Provider是怎麼知道Eureka Server的地址呢?
其實這部分的主體邏輯和3.3.7 How Peer Nodes are Discovered幾乎是同樣的。
也是默認從配置文件讀取,若是須要更靈活的控制,能夠經過override getEurekaServerServiceUrls
方法來提供本身的實現。按期更新頻率能夠經過eureka.client.eurekaServiceUrlPollIntervalSeconds
配置。
Service Consumer這塊的實現相對就簡單一些,由於它只涉及到從Eureka Server獲取服務列表和更新服務列表。
Service Consumer在啓動時會從Eureka Server獲取全部服務列表,並在本地緩存。須要注意的是,須要確保配置eureka.client.shouldFetchRegistry
=true。
因爲在本地有一份緩存,因此須要按期更新,按期更新頻率能夠經過eureka.client.registryFetchIntervalSeconds
配置。
Service Consumer和Service Provider同樣,也有一個如何知道Eureka Server地址的問題。
其實因爲Service Consumer和Service Provider本質上使用的是同一個Eureka客戶端,因此這部分邏輯是同樣的,這裏就再也不贅述了。詳細信息見3.4.4節。
本文主要介紹了Eureka的實現思路,經過深刻了解Eureka Server、Service Provider、Service Consumer的實現,咱們清晰地看到了服務註冊、發現的一系列過程和實現方式。
相信對正在使用Eureka的同窗會有一些幫助,同時但願對暫不使用Eureka的同窗也能有必定的啓發,畢竟服務註冊、發現仍是比較基礎和通用的,瞭解了實現方式後,在使用上應該能更駕輕就熟一些吧~