在看具體原始碼前,我們先回顧一下之前我們所實現的內容,從而找一個合適的切入口去分析。首先,服務註冊中心、服務提供者、服務消費者這三個主要元素來說,後兩者(也就是Eureka客戶端)在整個執行機制中是大部分通訊行為的主動發起者,而註冊中心主要是處理請求的接收者。所以,我們可以從Eureka的客戶端作為入口看看它是如何完成這些主動通訊行為的。
我們在將一個普通的Spring Boot應用註冊到Eureka Server中,或是從Eureka Server中獲取服務列表時,主要就做了兩件事:
在應用主類中配置了@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,我們可以發現有一個類和一個介面。通過梳理可以得到如下圖的關係:
其中,左邊的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> getServiceUrlsMapFromConfig( EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) { Map<String, List> 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
在上面的函式中,我們可以發現客戶端依次載入了兩個內容,第一個是Region,第二個是Zone,從其載入邏上我們可以判斷他們之間的關係:
通過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是一對多的關係。 public String[] getAvailabilityZones(String region) { String value = this.availabilityZones.get(region); if (value == null) { value = DEFAULT_ZONE; } return value.split(","); } ServiceUrls
在獲取了Region和Zone資訊之後,才開始真正載入Eureka Server的具體地址。它根據傳入的引數按一定演算法確定載入位於哪一個Zone配置的serviceUrls。
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); String zone = availZones[myZoneOffset]; List serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); 具體獲取serviceUrls的實現,我們可以詳細檢視getEurekaServerServiceUrls函式的具體實現類EurekaClientConfigBean,該類是EurekaClientConfig和EurekaConstants介面的實現,用來載入配置檔案中的內容,這裡有非常多有用的資訊,這裡我們先說一下此處我們關心的,關於defaultZone的資訊。通過搜尋defaultZone,我們可以很容易的找到下面這個函式,它具體實現了,如何解析該引數的過程,通過此內容,我們就可以知道,eureka.client.serviceUrl.defaultZone屬性可以配置多個,並且需要通過逗號分隔。
public List 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 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架構的搭建過程及如何運用於企業專案。
原始碼來源:http://minglisoft.cn/honghu/technology.html