天天看點

Eureka主動下線機制eureka幾種主動下線服務的方式程式入口Eureka-Server本文小結

本文來說下Eureka主動下線機制

文章目錄

  • eureka幾種主動下線服務的方式
    • 直接停掉服務
    • 向eureka注冊中心發送delete請求
    • 用戶端主動通知注冊中心下線
  • 程式入口
  • Eureka-Server
  • 本文小結

.

eureka幾種主動下線服務的方式

直接停掉服務

預設情況下,如果Eureka Server在90秒沒有收到Eureka客戶的續約,它會将執行個體從其系統資料庫中删除。但這種做法的不好之處在于, 用戶端已經停止了運作,但仍然在注冊中心的清單中。 雖然通過一定的負載均衡政策或使用熔斷器可以讓服務正常進行,但有沒有方法讓注冊中心馬上知道服務已經下線呢?

向eureka注冊中心發送delete請求

為了讓注冊中心馬上知道服務要下線, 可以向eureka 注冊中心發送delete 請求
格式為 /eureka/apps/{application.name}/
           
Eureka主動下線機制eureka幾種主動下線服務的方式程式入口Eureka-Server本文小結
Eureka主動下線機制eureka幾種主動下線服務的方式程式入口Eureka-Server本文小結

值得注意的是,Eureka用戶端每隔一段時間(預設30秒)會發送一次心跳到注冊中心續約。如果通過這種方式下線了一個服務,而沒有及時停掉的話,該服務很快又會回到服務清單中。

用戶端主動通知注冊中心下線

如果你的eureka用戶端是是一個spring boot應用,可以通過調用以下代碼通知注冊中心下線
@RestController
public class HelloController {

   @Autowired
   private DiscoveryClient client;
 
   @RequestMapping(value = "/hello", method = RequestMethod.GET)
   public String index() {
    
      List<ServiceInstance> instances = client.getInstances("hello-service"); 
      return "Hello World";
   }
 
   @RequestMapping(value = "/offline", method = RequestMethod.GET)
   public void offLine(){
   
      DiscoveryManager.getInstance().shutdownComponent();
   } 
}
           

程式入口

com.netflix.discovery.DiscoverClient
@PreDestroy
 @Override
 public synchronized void shutdown() {
     if (isShutdown.compareAndSet(false, true)) {
         logger.info("Shutting down DiscoveryClient ...");
 
         if (statusChangeListener != null && applicationInfoManager != null) {
             applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
         }
        // 取消定時任務(心跳,緩存重新整理等)
        cancelScheduledTasks();

        // 如果app注冊過,那麼需要取消注冊,也就說需要主動下線
       if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
             // 設定執行個體的狀态為DOWN
            applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
            // 執行下線
            unregister();
        }

        if (eurekaTransport != null) {
            eurekaTransport.shutdown();
        }

        heartbeatStalenessMonitor.shutdown();
        registryStalenessMonitor.shutdown();

        logger.info("Completed shut down of DiscoveryClient");
    }
}


           

在這個類被容器銷毀的時候,會執行這個方法,執行主動下線的代碼

unregister()
void unregister() {

     // 如果是非注冊的執行個體,那麼eurekaTransport可能是為空的,是以做一下判斷
     if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
         try {
             logger.info("Unregistering ...");
             // 發送HTTP請求,到服務端,請求下線 , 接口位址:  "apps/" + appName + '/' + id;
             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);
        }
    }
}
           

Eureka-Server

com.netflix.eureka.resources.InstanceResource
@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();
    }
}
           
org.springframework.cloud.netflix.eureka.server.InstanceRegistry
@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {

    //釋出取消事件
    handleCancelation(appName, serverId, isReplication);
    // 調用父類的取消方法
    return super.cancel(appName, serverId, isReplication);
 }
 
 private void handleCancelation(String appName, String id, boolean isReplication) {
 
    log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
   publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
}
           
父類PeerAwareInstanceRegistryImpl
@Override
public boolean cancel(final String appName, final String id,
                       final boolean isReplication) {
     // 執行父類的取消方法
     if (super.cancel(appName, id, isReplication)) {
         //叢集同步資訊
         replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
         // Eureka-Server的保護機制
         synchronized (lock) {
            if (this.expectedNumberOfRenewsPerMin > 0) {
                // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
                this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
                this.numberOfRenewsPerMinThreshold =
                        (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
            }
        }
        return true;
    }
    return false;
}
           
PeerAwareInstanceRegistryImpl 的父類AbstractInstanceRegistry中是取消下線的主要邏輯。
@Override
public boolean cancel(String appName, String id, boolean isReplication) {

     return internalCancel(appName, id, isReplication);
 }
 
 /**
  * {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
  * cancel request is replicated to the peers. This is however not desired for expires which would be counted
  * in the remote peers as valid cancellations, so self preservation mode would not kick-in.
 */

protected boolean internalCancel(String appName, String id, boolean isReplication) {
    try {
        // 讀鎖
        read.lock();
        // 添加取消次數給監控資訊,這裡是個枚舉類,收集了取消次數
        CANCEL.increment(isReplication);
        // 從本地的CurrentHashMap中,擷取目前執行個體對應的Lease資訊
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToCancel = null;
        if (gMap != null) {
            // 移除資訊 , 如果用戶端是叢集模式,此處僅移除這個執行個體ID對應的資訊
            leaseToCancel = gMap.remove(id);
        }
        // 添加取消資訊到取消隊列,主要用于運維界面的資訊統計
        synchronized (recentCanceledQueue) {
            recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
        }
        //移除這個執行個體ID對應的instance狀态
        InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
        if (instanceStatus != null) {
            logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
        }
        if (leaseToCancel == null) {
            // 如果資訊不存在,則說明這個執行個體從來沒有注冊過來,或者已經下線了。
            CANCEL_NOT_FOUND.increment(isReplication);
            logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
            return false;
        } else {
            // 更新Lease執行個體資訊裡面的evictionTimestamp這個時間戳,标明下線時間
            leaseToCancel.cancel();
            InstanceInfo instanceInfo = leaseToCancel.getHolder();
            String vip = null;
            String svip = null;
            // 擷取VIP,SVIP,然後把instance的變化加入執行個體變化隊列中
            if (instanceInfo != null) {
                instanceInfo.setActionType(ActionType.DELETED);
                recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                instanceInfo.setLastUpdatedTimestamp();
                vip = instanceInfo.getVIPAddress();
                svip = instanceInfo.getSecureVipAddress();
            }
            // 顯示的清楚緩存 , guava的API
            invalidateCache(appName, vip, svip);
            logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
            return true;
        }
    } finally {
        read.unlock();
    }
}
           

綜上可以看到,首先是從本地的CurrentHashMap中擷取目前appName對應的的Map資訊,最後通過機器ID,擷取要下線的機器對應的Lease,修改Lease的evictionTimestamp , 也就是設定下線時間為目前時間點。

public void cancel() {

    if (evictionTimestamp <= 0) {
        evictionTimestamp = System.currentTimeMillis();
    }
}
           

主動下線還是比較簡單的。

本文小結

本文詳細介紹了Eureka主動下線的主要源碼和機制。

繼續閱讀