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

芋道原始碼_以德服人_不服就幹發表於2019-02-19

摘要: 原創出處 http://www.iocoder.cn/Eureka/instance-registry-cancel/ 「芋道原始碼」歡迎轉載,保留摘要,謝謝!

本文主要基於 Eureka 1.8.X 版本


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

???關注**微信公眾號:【芋道原始碼】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
  3. 您對於原始碼的疑問每條留言將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢
  4. 新的原始碼解析文章實時收到通知。每週更新一篇左右
  5. 認真的原始碼交流微信群。

1. 概述

本文主要分享 Eureka-Client 向 Eureka-Server 下線應用例項的過程

FROM 《深度剖析服務發現元件Netflix Eureka》 二次編輯

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

  • 藍框部分,為本文重點。
  • 藍框部分,Eureka-Server 叢集間複製註冊的應用例項資訊,不在本文內容範疇。

推薦 Spring Cloud 書籍

推薦 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<Void> 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<Void> 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()).headers(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: }
    複製程式碼

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<String, Lease<InstanceInfo>> gMap = registry.get(appName);
 14:         Lease<InstanceInfo> leaseToCancel = null;
 15:         if (gMap != null) {
 16:             leaseToCancel = gMap.remove(id);
 17:         }
 18:         // 新增到 最近取消註冊的除錯佇列
 19:         synchronized (recentCanceledQueue) {
 20:             recentCanceledQueue.add(new Pair<Long, String>(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<Pair<Long, String>> 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<RecentlyChangedItem> recentlyChangedQueue = new ConcurrentLinkedQueue<RecentlyChangedItem>();
    複製程式碼
  • 第 47 行 :設定響應快取( ResponseCache )過期,在《Eureka 原始碼解析 —— 應用例項註冊發現 (六)之全量獲取》詳細解析。

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

  • 第 53 行 :釋放鎖。

666. 彩蛋

知識星球

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

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

相關文章