點選上方“芋道原始碼”,選擇“置頂公眾號”
技術文章第一時間送達!
原始碼精品專欄
本文主要基於 Eureka 1.8.X 版本
-
1. 概述
-
2. Eureka-Client 發起下線
-
3. Eureka-Server 接收下線
-
3.1 接收下線請求
-
3.2 下線應用實體資訊
-
666. 彩蛋
1. 概述
本文主要分享 Eureka-Client 向 Eureka-Server 下線應用實體的過程。
FROM 《深度剖析服務發現元件Netflix Eureka》 二次編輯
-
藍框部分,為本文重點。
-
非藍框部分,Eureka-Server 叢集間複製註冊的應用實體資訊,不在本文內容範疇。
推薦 Spring Cloud 書籍:
-
請支援正版。下載盜版,等於主動編寫低階 BUG 。
-
程式猿DD —— 《Spring Cloud微服務實戰》
-
周立 —— 《Spring Cloud與Docker微服務架構實戰》
推薦 Spring Cloud 影片:
-
Java 微服務實踐 – Spring Boot
-
Java 微服務實踐 – Spring Cloud
-
Java 微服務實踐 – Spring Boot / Spring Cloud
2. Eureka-Client 發起下線
應用實體關閉時,Eureka-Client 向 Eureka-Server 發起下線應用實體。需要滿足如下條件才可發起:
-
配置
eureka.registration.enabled = true
,應用實體開啟註冊開關。預設為false
。 -
配置
eureka.shouldUnregisterOnShutdown = true
,應用實體開啟關閉時下線開關。預設為true
。
實現程式碼如下:
// DiscoveryClient.java
public synchronized void shutdown() {
// ... 省略無關程式碼
// If APPINFO was registered
if (applicationInfoManager != null
&& clientConfig.shouldRegisterWithEureka() // eureka.registration.enabled = true
&& clientConfig.shouldUnregisterOnShutdown()) { // eureka.shouldUnregisterOnShutdown = true
applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
unregister();
}
}
-
呼叫
ApplicationInfoManager#setInstanceStatus(...)
方法,設定應用實體為關閉( DOWN )。 -
呼叫
#unregister()
方法,實現程式碼如下:// DiscoveryClient.java
void unregister() {
// It can be null if shouldRegisterWithEureka == false
if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
try {
logger.info("Unregistering ...");
EurekaHttpResponsehttpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
logger.info(PREFIX + appPathIdentifier + " - deregister status: " + httpResponse.getStatusCode());
} catch (Exception e) {
logger.error(PREFIX + appPathIdentifier + " - de-registration failed" + e.getMessage(), e);
}
}
}
// AbstractJerseyEurekaHttpClient.java
@Override
public EurekaHttpResponsecancel(String appName, String id) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder.delete(ClientResponse.class);
return anEurekaHttpResponse(response.getStatus()).essay-headers(essay-headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
} -
呼叫
AbstractJerseyEurekaHttpClient#cancel(...)
方法,DELETE
請求 Eureka-Server 的apps/${APP_NAME}/${INSTANCE_INFO_ID}
介面,實現應用實體資訊的下線。
3. Eureka-Server 接收下線
3.1 接收下線請求
com.netflix.eureka.resources.InstanceResource
,處理單個應用實體資訊的請求操作的 Resource ( Controller )。
下線應用實體資訊的請求,對映 InstanceResource#cancelLease()
方法,實現程式碼如下:
@DELETE
public Response cancelLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
// 下線
boolean isSuccess = registry.cancel(app.getName(), id, "true".equals(isReplication));
if (isSuccess) { // 下線成功
logger.debug("Found (Cancel): " + app.getName() + " - " + id);
return Response.ok().build();
} else { // 下線成功
logger.info("Not Found (Cancel): " + app.getName() + " - " + id);
return Response.status(Status.NOT_FOUND).build();
}
}
-
呼叫
PeerAwareInstanceRegistryImpl#cancel(...)
方法,下線應用實體。實現程式碼如下:1: @Override
2: public boolean cancel(final String appName, final String id,
3: final boolean isReplication) {
4: if (super.cancel(appName, id, isReplication)) { // 下線
5: // Eureka-Server 複製
6: replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
7: // 減少 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin`
8: synchronized (lock) {
9: if (this.expectedNumberOfRenewsPerMin > 0) {
10: // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
11: this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
12: this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
13: }
14: }
15: return true;
16: }
17: return false;
18: } -
第 4 行 :呼叫父類
AbstractInstanceRegistry#cancel(...)
方法,下線應用實體資訊。 -
第 6 行 :Eureka-Server 複製下線操作,在 《Eureka 原始碼解析 —— Eureka-Server 叢集同步》 有詳細解析。
-
第 7 至 14 行 :減少
numberOfRenewsPerMinThreshold
、expectedNumberOfRenewsPerMin
,自我保護機制相關,在 《Eureka 原始碼解析 —— 應用實體註冊發現(四)之自我保護機制》 有詳細解析。
3.2 下線應用實體資訊
呼叫 AbstractInstanceRegistry#cancel(...)
方法,下線應用實體資訊,實現程式碼如下:
1: @Override
2: public boolean cancel(String appName, String id, boolean isReplication) {
3: return internalCancel(appName, id, isReplication);
4: }
5:
6: protected boolean internalCancel(String appName, String id, boolean isReplication) {
7: try {
8: // 獲得讀鎖
9: read.lock();
10: // 增加 取消註冊次數 到 監控
11: CANCEL.increment(isReplication);
12: // 移除 租約對映
13: Map> gMap = registry.get(appName);
14: Lease leaseToCancel = null;
15: if (gMap != null) {
16: leaseToCancel = gMap.remove(id);
17: }
18: // 新增到 最近取消註冊的除錯佇列
19: synchronized (recentCanceledQueue) {
20: recentCanceledQueue.add(new Pair(System.currentTimeMillis(), appName + "(" + id + ")"));
21: }
22: // 移除 應用實體改寫狀態對映
23: InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
24: if (instanceStatus != null) {
25: logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
26: }
27: // 租約不存在
28: if (leaseToCancel == null) {
29: CANCEL_NOT_FOUND.increment(isReplication); // 新增 取消註冊不存在 到 監控
30: logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
31: return false; // 失敗
32: } else {
33: // 設定 租約的取消註冊時間戳
34: leaseToCancel.cancel();
35: // 新增到 最近租約變更記錄佇列
36: InstanceInfo instanceInfo = leaseToCancel.getHolder();
37: String vip = null;
38: String svip = null;
39: if (instanceInfo != null) {
40: instanceInfo.setActionType(ActionType.DELETED);
41: recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
42: instanceInfo.setLastUpdatedTimestamp();
43: vip = instanceInfo.getVIPAddress();
44: svip = instanceInfo.getSecureVipAddress();
45: }
46: // 設定 響應快取 過期
47: invalidateCache(appName, vip, svip);
48: logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
49: return true; // 成功
50: }
51: } finally {
52: // 釋放鎖
53: read.unlock();
54: }
55: }
-
第 9 行 :獲取讀鎖。在 《Eureka原始碼解析 —— 應用實體註冊發現 (九)之歲月是把萌萌的讀寫鎖》 詳細解析。
-
第 10 至 11 行 :增加下線次數到監控。配合 Netflix Servo 實現監控資訊採集。
-
第 12 至 17 行 :移除租約對映(
registry
)。 -
第 18 至 21 行 :新增到最近下線的除錯佇列(
recentCanceledQueue
),用於 Eureka-Server 運維介面的顯示,無實際業務邏輯使用。實現程式碼如下:/**
* 最近取消註冊的除錯佇列
* key :新增時的時間戳
* value :字串 = 應用名(應用實體資訊編號)
*/
private final CircularQueue> recentCanceledQueue; -
第 22 至 26 行 :移除應用實體改寫狀態對映。在《應用實體註冊發現 (八)之改寫狀態》詳細解析。
-
第 27 至 31 行 :租約不存在,傳回下線失敗(
false
)。 -
第 34 行 :呼叫
Lease#cancel()
方法,取消租約。實現程式碼如下:// Lease.java
public void cancel() {
if (evictionTimestamp <= 0) {
evictionTimestamp = System.currentTimeMillis();
}
} -
第 35 至 45 行 :設定應用實體資訊的操作型別為新增,並新增到最近租約變更記錄佇列(
recentlyChangedQueue
)。recentlyChangedQueue
用於註冊資訊的增量獲取,在《應用實體註冊發現 (七)之增量獲取》詳細解析。實現程式碼如下:/**
* 最近租約變更記錄佇列
*/
private ConcurrentLinkedQueuerecentlyChangedQueue = new ConcurrentLinkedQueue (); -
第 47 行 :設定響應快取( ResponseCache )過期,在《Eureka 原始碼解析 —— 應用實體註冊發現 (六)之全量獲取》詳細解析。
-
第 49 行 :傳回下線失敗(
false
)。 -
第 53 行 :釋放鎖。
666. 彩蛋
水更一篇,下一篇租約過期!走起。
胖友,分享我的公眾號( 芋道原始碼 ) 給你的胖友可好?