微服務架構:Nacos本地緩存 PK 微服務優雅下線

 前言

今天聊聊正常關閉服務時如何讓微服務優雅下線。程序員

爲何說是優雅下線?咱們知道在分佈式應用中爲了知足CAP原則中的A(可用性),像Nacos、Eureka等註冊中心的客戶端都會進行實例列表的緩存。當正常關閉應用時,雖然能夠主動調用註冊中心進行註銷,但這些客戶端緩存的實例列表仍是要等一段時間纔會失效。web

上述狀況就有可能致使服務請求到已經被關閉的實例上,雖然經過重試機制能夠解決掉這個問題,但這種解決方案會出現重試,在必定程度上會致使用戶側請求變慢。這時就須要進行優雅的下線操做了。面試

下面咱們先從一般關閉進程的幾種方式聊起。spring

方式一:基於kill命令

Spring Cloud自己對關閉服務是有支持的,當經過kill命令關閉進程時會主動調用Shutdown hook來進行當前實例的註銷。使用方式:json

kill Java進程ID

這種方式是藉助Spring Cloud的Shutdown hook機制(本質是Spring Boot提供,Spring Cloud服務發現功能進行具體註銷實現),在關閉服務以前會對Nacos、Eureka等服務進行註銷。但這個註銷只是告訴了註冊中心,客戶端的緩存可能須要等幾秒(Nacos默認爲5秒)以後才能感知到。緩存

這種Shutdown hook機制不只適用於kill命令,還適用於程序正常退出、使用System.exit()、終端使用Ctrl + C等。但不適用於kill -9 這樣強制關閉或服務器宕機等場景。服務器

這種方案雖然比直接掛掉要等15秒縮短了時間,相對好一些,但本質上並無解決客戶端緩存的問題,不建議使用。app

方式二:基於/shutdown端點

在Spring Boot中,提供了/shutdown端點,基於此也能夠實現優雅停機,但本質上與第一種方式相同,都是基於Shutdown hook來實現的。在處理完基於Shutdown hook的邏輯以後,也會進行服務的關閉,但一樣面臨客戶端緩存的問題,所以,也不推薦使用。curl

這種方式首先須要在項目中引入對應的依賴:分佈式

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

而後在項目中配置開啓/shutdown端點:

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

而後停服時請求對應的端點,這裏採用curl命令示例:

curl -X http://實例服務地址/actuator/shutdown

方式三:基於/pause端點

Spring Boot一樣提供了/pause端點(Spring Boot Actuator提供),經過/pause端點,能夠將/health爲UP狀態的實例修改成Down狀態。

基本操做就是在配置文件中進行pause端點的開啓:

management:
  endpoint:
    # 啓用pause端點
    pause:
      enabled: true
    # pause端點在某些版本下依賴restart端點
    restart:
      enabled: true
  endpoints:
    web:
      exposure:
        include: pause,restart

而後發送curl命令,便可進行服務的終止。注意這裏須要採用POST請求。

關於/pause端點的使用,不一樣的版本差別很大。筆者在使用Spring Boot 2.4.2.RELEASE版本時發現根本沒法生效,查了Spring Boot和Spring Cloud項目的Issues發現,這個問題從2.3.1.RELEASE就存在。目前看應該是在最新版本中Web Server的管理改成SmartLifecycle的緣由,而Spring Cloud對此貌似放棄了支持(有待考察),最新的版本調用/pause端點無任何反應。

鑑於上述版本變更過大的緣由,不建議使用/pause端點進行微服務的下線操做,但使用/pause端點的整個思路仍是值得借鑑的。

基本思路就是:當調用/pause端點以後,微服務的狀態會從UP變爲DOWN,而服務自己仍是能夠正常提供服務。當微服務被標記爲DOWN狀態以後,會從註冊中心摘除,等待一段時間(好比5秒),當Nacos客戶端緩存的實例列表更新了,再進行停服處理。

這個思路的核心就是:先將微服務的流量切換掉,而後再關閉或從新發布。這就解決了正常發佈時客戶端緩存實例列表的問題。

基於上述思路,其實本身也能夠實現相應的功能,好比提供一個Controller,先調用該Controller中的方法將當前實例從Nacos中註銷,而後等待5秒,再經過腳本或其餘方式將服務關閉掉。

方式四:基於/service-registry端點

方式三中提到的方案若是Spring Cloud可以直接支持,那就更好了。這不,Spring Cloud提供了/service-registry端點。但從名字就能夠知道專門針對服務註冊實現的一個端點。

在配置文件中開啓/service-registry端點:

management:
  endpoints:
    web:
      exposure:
        include: service-registry
      base-path: /actuator
  endpoint:
    serviceregistry:
      enabled: true

訪問端點能夠查看到開啓了以下端點:

{
    "_links": {
        "self": {
            "href": "http://localhost:8081/actuator",
            "templated": false
        },
        "serviceregistry": {
            "href": "http://localhost:8081/actuator/serviceregistry",
            "templated": false
        }
    }
}

經過curl命令來進行服務狀態的修改:

curl -X "POST" "http://localhost:8081/actuator/serviceregistry?status=DOWN" -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"

執行上述命令以前,查看Nacos對應實例狀態爲:

fe9ede237dd92b12383f821bba83eaa3.png

能夠看到實例詳情中的按鈕爲「下線」也就是說目前處於UP狀態。當執行完上述curl命令以後,實例詳情中的按鈕爲「上線」,說明實例已經下線了。

59bafeeb92f5e486fe17f16007904d6d.png

上述命令就至關於咱們在Nacos管理後臺手動的操做了實例的上下線。

固然,上述狀況是基於Spring Cloud和Nacos的模式實現的,本質上Spring Cloud是定義了一個規範,好比全部的註冊中心都須要實現ServiceRegistry接口,同時基於ServiceRegistry這個抽象還定義了通用的Endpoint:

@Endpoint(id = "serviceregistry")
public class ServiceRegistryEndpoint {

   private final ServiceRegistry serviceRegistry;

   private Registration registration;

   public ServiceRegistryEndpoint(ServiceRegistry<?> serviceRegistry) {
      this.serviceRegistry = serviceRegistry;
   }

   public void setRegistration(Registration registration) {
      this.registration = registration;
   }

   @WriteOperation
   public ResponseEntity<?> setStatus(String status) {
      Assert.notNull(status, "status may not by null");

      if (this.registration == null) {
         return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
      }

      this.serviceRegistry.setStatus(this.registration, status);
      return ResponseEntity.ok().build();
   }

   @ReadOperation
   public ResponseEntity getStatus() {
      if (this.registration == null) {
         return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
      }

      return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration));
   }

}

咱們上面調用的Endpoint即是經過上面代碼實現的。因此不只Nacos,只要基於Spring Cloud集成的註冊中心,本質上都是支持這種方式的服務下線的。

小結

不少項目都逐步在進行微服務化改造,但一旦由於微服務系統,將面臨着更復雜的狀況。本篇文章重點基於Nacos在Spring Cloud體系中優雅下線來爲你們剖析了一個微服務實戰中常見的問題及解決方案。你是否在使用微服務,你又是否注意到這一點了?想學更多微服務實戰,啥也不說,關注吧。

最後

最近我整理了整套《JAVA核心知識點總結》,說實話 ,做爲一名Java程序員,不論你需不須要面試都應該好好看下這份資料。拿到手老是不虧的~個人很多粉絲也所以拿到騰訊字節快手等公司的Offer

Java進階之路羣,找管理員獲取哦-!

d7f3f040e400da97c8baba2cd576341d.png

8a5f22206f0154fb0ff3d0a316729c98.png

相關文章
相關標籤/搜索