在看具體源碼前,咱們先回顧一下以前咱們所實現的內容,從而找一個合適的切入口去分析。首先,服務註冊中心、服務提供者、服務消費者這三個主要元素來講,後二者(也就是Eureka客戶端)在整個運行機制中是大部分通訊行爲的主動發起者,而註冊中心主要是處理請求的接收者。因此,咱們能夠從Eureka的客戶端做爲入口看看它是如何完成這些主動通訊行爲的。算法
咱們在將一個普通的Spring Boot應用註冊到Eureka Server中,或是從Eureka Server中獲取服務列表時,主要就作了兩件事:spring
@EnableDiscoveryClient
註解application.properties
中用eureka.client.serviceUrl.defaultZone
參數指定了服務註冊中心的位置順着上面的線索,咱們先查看@EnableDiscoveryClient
的源碼以下:架構
/** * Annotation to enable a DiscoveryClient implementation. * @author Spencer Gibb */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { }
從該註解的註釋咱們能夠知道:該註解用來開啓DiscoveryClient
的實例。經過搜索DiscoveryClient
,咱們能夠發現有一個類和一個接口。經過梳理能夠獲得以下圖的關係:app
其中,左邊的org.springframework.cloud.client.discovery.DiscoveryClient
是Spring Cloud的接口,它定義了用來發現服務的經常使用抽象方法,而org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient
是對該接口的實現,從命名來就能夠判斷,它實現的是對Eureka發現服務的封裝。因此EurekaDiscoveryClient
依賴了Eureka的com.netflix.discovery.EurekaClient
接口,EurekaClient
繼承了LookupService
接口,他們都是Netflix開源包中的內容,它主要定義了針對Eureka的發現服務的抽象方法,而真正實現發現服務的則是Netflix包中的com.netflix.discovery.DiscoveryClient
類。框架
那麼,咱們就看看來詳細看看DiscoveryClient
類。先解讀一下該類頭部的註釋有個整體的瞭解,註釋的大體內容以下:函數
這個類用於幫助與Eureka Server互相協做。 Eureka Client負責了下面的任務: - 向Eureka Server註冊服務實例 - 向Eureka Server爲租約續期 - 當服務關閉期間,向Eureka Server取消租約 - 查詢Eureka Server中的服務實例列表 Eureka Client還須要配置一個Eureka Server的URL列表。 |
在具體研究Eureka Client具體負責的任務以前,咱們先看看對Eureka Server的URL列表配置在哪裏。根據咱們配置的屬性名:eureka.client.serviceUrl.defaultZone
,經過serviceUrl
咱們找到該屬性相關的加載屬性,可是在SR5版本中它們都被@Deprecated
標註了,並在注視中能夠看到@link
到了替代類com.netflix.discovery.endpoint.EndpointUtils
,咱們能夠在該類中找到下面這個函數:
微服務
public static Map<String, List<String>> getServiceUrlsMapFromConfig( EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) { Map<String, List<String>> orderedUrls = new LinkedHashMap<>(); String region = getRegion(clientConfig); String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); if (availZones == null || availZones.length == 0) { availZones = new String[1]; availZones[0] = DEFAULT_ZONE; } …… int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); String zone = availZones[myZoneOffset]; List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); if (serviceUrls != null) { orderedUrls.put(zone, serviceUrls); } …… return orderedUrls; }
在上面的函數中,咱們能夠發現客戶端依次加載了兩個內容,第一個是Region,第二個是Zone,從其加載邏上咱們能夠判斷他們之間的關係:this
getRegion
函數,咱們能夠看到它從配置中讀取了一個Region返回,因此一個微服務應用只能夠屬於一個Region,若是不特別配置,就默認爲default。若咱們要本身設置,能夠經過eureka.client.region
屬性來定義。public static String getRegion(EurekaClientConfig clientConfig) { String region = clientConfig.getRegion(); if (region == null) { region = DEFAULT_REGION; } region = region.trim().toLowerCase(); return region; }
經過getAvailabilityZones
函數,咱們能夠知道當咱們沒有特別爲Region配置Zone的時候,將默認採用defaultZone,這也是咱們以前配置參數eureka.client.serviceUrl.defaultZone
的由來。若要爲應用指定Zone,咱們能夠經過eureka.client.availability-zones
屬性來進行設置。從該函數的return
內容,咱們能夠Zone是能夠有多個的,而且經過逗號分隔來配置。由此,咱們能夠判斷Region與Zone是一對多的關係。spa
public String[] getAvailabilityZones(String region) { String value = this.availabilityZones.get(region); if (value == null) { value = DEFAULT_ZONE; } return value.split(","); }
在獲取了Region和Zone信息以後,纔開始真正加載Eureka Server的具體地址。它根據傳入的參數按必定算法肯定加載位於哪個Zone配置的serviceUrls。.net
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); String zone = availZones[myZoneOffset]; List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
具體獲取serviceUrls的實現,咱們能夠詳細查看getEurekaServerServiceUrls
函數的具體實現類EurekaClientConfigBean
,該類是EurekaClientConfig
和EurekaConstants
接口的實現,用來加載配置文件中的內容,這裏有很是多有用的信息,這裏咱們先說一下此處咱們關心的,關於defaultZone
的信息。經過搜索defaultZone
,咱們能夠很容易的找到下面這個函數,它具體實現了,如何解析該參數的過程,經過此內容,咱們就能夠知道,eureka.client.serviceUrl.defaultZone
屬性能夠配置多個,而且須要經過逗號分隔。
public List<String> getEurekaServerServiceUrls(String myZone) { String serviceUrls = this.serviceUrl.get(myZone); if (serviceUrls == null || serviceUrls.isEmpty()) { serviceUrls = this.serviceUrl.get(DEFAULT_ZONE); } if (!StringUtils.isEmpty(serviceUrls)) { final String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls); List<String> eurekaServiceUrls = new ArrayList<>(serviceUrlsSplit.length); for (String eurekaServiceUrl : serviceUrlsSplit) { if (!endsWithSlash(eurekaServiceUrl)) { eurekaServiceUrl += "/"; } eurekaServiceUrls.add(eurekaServiceUrl); } return eurekaServiceUrls; } return new ArrayList<>(); }
當客戶端在服務列表中選擇實例進行訪問時,對於Zone和Region遵循這樣的規則:優先訪問同本身一個Zone中的實例,其次才訪問其餘Zone中的實例。經過Region和Zone的兩層級別定義,配合實際部署的物理結構,咱們就能夠有效的設計出區域性故障的容錯集羣。
從如今開始,我這邊會將近期研發的springcloud微服務雲架構的搭建過程和精髓記錄下來,幫助更多有興趣研發spring cloud框架的朋友,但願能夠幫助更多的好學者。你們來一塊兒探討spring cloud架構的搭建過程及如何運用於企業項目。