歡迎光臨
每天分享高質量文章

註冊中心 Eureka 原始碼解析 —— 應用實體註冊發現 (三)之下線

點擊上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

本文主要基於 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 ...");
              EurekaHttpResponse httpResponse = 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 EurekaHttpResponse cancel(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 ConcurrentLinkedQueue recentlyChangedQueue = new ConcurrentLinkedQueue();
  • 第 47 行 :設置響應快取( ResponseCache )過期,在《Eureka 原始碼解析 —— 應用實體註冊發現 (六)之全量獲取》詳細解析。

  • 第 49 行 :傳回下線失敗( false )。

  • 第 53 行 :釋放鎖。

666. 彩蛋

知識星球

水更一篇,下一篇租約過期!走起。

胖友,分享我的公眾號( 芋道原始碼 ) 給你的胖友可好?

赞(0)

分享創造快樂