Spring Cloud 入門到進階 - 01 Eureka 服務實例的健康自檢 (下)


博主整理的SpringCloud系列目錄:>>戳這裏<<

內容關聯篇(建議先看):
Spring Cloud 入門到進階 - 01 Eureka介紹及簡單服務搭建(上)
Spring Cloud 入門到進階 - 01 Eureka集羣搭建(中)


一、Eureka 服務實例的健康自檢

在默認情況下, Eureka 的客戶端每隔 30 秒會發送 次心跳給服務器端,告知它仍然存活。但是,在實際環境中有可能出現這種情況,客戶端表面上可以正常發送心跳,但實際上服務是不可用的。

例如一個需要訪問數據的服務提供者,表面上可以正常響應,但是數據庫己經無法訪問:又如,服務提供者需要訪問第三方的服務,而這些服務早己失效。對於這些情況,應當告訴服務器當前客戶 的狀態,調用者或者其他客戶端無法獲取這些有問題的實例。實現該功能,可以使用 Eureka 的健康檢查控制器。

1、程序結構

將前面(傳送門)我們學習並搭建的第一個Eureka項目中的服務器、服務提供者和服務調用者進行復制,命名如下。

  • health-handler-server 本例的 Eureka 服務器。
  • health-handler-provider 本例的服務提供者客戶端。
  • health-handler-invoker 本例的服務調用者客戶端。
    在這裏插入圖片描述

假設在實際環境中,服務提供者模塊需要訪問數據庫,本例的 health-handler-provider 模塊,將是進行健康自檢的模塊。

2、使用 Spring Boot Actuator

Spring Boot Actuator 模塊主要用於系統監控,當應用程序整合了 Actuator 後,它就會自動提供多個服務端點,這些端點可以讓外部看到應用程序的健康情況。

在本例中使用的是 /health 端點。修改 health-handler-providerpom.xml 文件,加入以下依賴:

<!-- 加入系統監控模塊 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

加入依賴後,先啓動 health-handler-server,再啓動 health-handler-provider。這兩個模塊與上篇的項目基本類似,僅僅修改了部分類名。

啓動完成後,在瀏覽器中訪問 http://localhost:8080/actuator/health,可以看到輸出如下:
在這裏插入圖片描述

3、實現應用健康自檢

如果一個客戶端本身沒有問題,但是該模塊所依賴的服務無法使用,那麼對於服務器以及其他客戶端來說,該客戶端也是不可用的,最常見的就是訪問數據庫的模塊。

我們需要做兩件事:第一,讓客戶端自己進行檢查,是否能連接數據庫;第二,將連接數據庫的結果與客戶端的狀態進行關聯,並且將狀態告訴服務器。

我們使用 Spring Boot Actuator 可以直接實現一個自定義的 HealthIndicator,根據是否能訪問數據庫,來決定應用自身的健康。

3.1 添加健康指示器

下面,我們爲服務提供者添加健康指示器。

package com.swotxu.hhprovider.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

/**
 * 模擬檢查數據庫連接是否成功的健康指示器
 * @Date: 2020/7/4 16:18
 * @Author: swotXu
 */
@Component
public class MyHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        Health.Builder builder = new Health.Builder();
        // 數據庫連接:成功返回 UP,失敗返回 DOWN
        return HealthController.canVisitDb?
                builder.up().build() : builder.down().build();
    }
}

爲了簡單起見,使用 HealthController 類的 canVisitDb 變量來模擬是否能連接上數據庫。該變量的相關代碼如下:

package com.swotxu.hhprovider.health;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Date: 2020/7/4 16:20
 * @Author: swotXu
 */
@RestController
public class HealthController {

    // 標識當前數據庫是否可以訪問
    static Boolean canVisitDb = false;

    @RequestMapping(value = "/db/{canVisitDb}", method = RequestMethod.GET)
    public String setConnectState(@PathVariable("canVisitDb") Boolean canVisitDb) {
        HealthController.canVisitDb = canVisitDb;
        return String.format("當前數據庫是否正常: %b", canVisitDb);
    }
}

控制器中的 canVisitDb 變量,可以通過 /db/false/db/true 兩個地址來修改,配合健康指示器一起使用。如果該值爲 true ,健康指示器將會返回“UP”狀態,反之則返回“DOWN”的狀態。

3.2 測試健康指示器

修改完後 ,啓動服務器( health-handler-server )以及服務提供者( health-handler-provider)。訪問 http://localhost:8080/actuator/health 可以看到服務提供者的健康狀態。
在這裏插入圖片描述
在這裏插入圖片描述

3.3 添加健康檢查處理器

如果服務提供者想把健康狀態告訴服務器,還需要實現“健康檢查處理器”。處理器會將應用的健康狀態保存到內存中,狀態一旦發生改變,就會重新向服務器進行註冊,其他的客戶端將拿不到這些不可用的實例。

下面,我們新建一個健康檢查處理器:

package com.swotxu.hhprovider.health;

import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;

/**
 * 健康檢查處理器,將數據庫是否可用的狀態通知給服務器
 *
 * @Date: 2020/7/4 17:10
 * @Author: swotXu
 */
@Slf4j
@Component
public class MyHealthCheckHandler implements HealthCheckHandler {

    @Autowired
    private MyHealthIndicator indicator;

    @Override
    public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
        Status status = indicator.health().getStatus();
        log.info("數據庫連接是否正常: {}", status);

        return Status.UP.equals(status)?
                InstanceInfo.InstanceStatus.UP : InstanceInfo.InstanceStatus.DOWN;
    }
}

在自定義的健康檢查處理器中,注入了前面編寫的健康指示器,根據健康指示器的結果來返回不同的狀態。

Eureka中會啓動一個定時器,定時刷新本地實例的信息,並且執行“處理器”中的 getStatus 方法,再將服務實例的狀態“更新”到服務器中。定時器默認 30 秒執行一次。

我們這裏爲了加快看到效果,可以修改 eureka.client.instance-info-replication-interval-seconds 配置。

修改服務提供者的 application.yml 配置如下:

spring:
  application:
    name: health-handler-provider
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka
    instance-info-replication-interval-seconds: 10  # 改爲每10秒向服務器提交一次健康信息

3.4 測試健康檢查處理器

  1. 我們啓動服務器,再啓動服務提供者,在瀏覽器中訪問 http://localhost:8761/ ,可以看到服務提供者的狀態爲 DOWN
    在這裏插入圖片描述
  2. 然後,我們訪問 http://localhost:8080/db/true ,將數據庫設置爲“可以連接”狀態。
    在這裏插入圖片描述
  3. 訪問 http://localhost:8080/actuator/health ,檢查是否設置成功。
    在這裏插入圖片描述
  4. 最後,等待10s以後,再訪問 8761 端口,可以看到服務提供者的狀態已經變爲 UP
    在這裏插入圖片描述

4、服務查詢

在上面我們講了,通過瀏覽器訪問 Eureka 界面可查看服務狀態的改變。接下來,我們通過修改服務調用者的代碼來查看應用健康自檢的效果。

4.1、修改服務調用者

修改服務調用者(health-handler-invoker 模塊),添加控制器,代碼如下:

package com.swotxu.hhinvoker;

import com.netflix.appinfo.InstanceInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * @Date: 2020/7/4 17:48
 * @Author: swotXu
 */
@RestController
@Configuration
public class HealthController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping(value = "/health", method = RequestMethod.GET)
    public String health() {
        StringBuilder sb = new StringBuilder();
        // 查詢服務列表
        List<ServiceInstance> instanceList = getServiceInstances();
        // 輸出服務信息及狀態
        for (ServiceInstance service : instanceList) {
            EurekaServiceInstance esi = (EurekaServiceInstance) service;
            InstanceInfo info = esi.getInstanceInfo();

            sb.append(String.format("AppName: %s ---- InstanceId: %s ---- Status: %s </br>"
                    , info.getAppName(), info.getInstanceId(), info.getStatus()));
        }
        return sb.toString();
    }

    /**
     * 查詢可以服務
     * @return
     */
    private List<ServiceInstance> getServiceInstances() {
        List<ServiceInstance> result = new ArrayList<>();

        List<String> idList = discoveryClient.getServices();
        for (String id : idList) {
            List<ServiceInstance> instances = discoveryClient.getInstances(id);
            result.addAll(instances);
        }
        return result;
    }
}

在客戶端中,如果需要查詢集羣中的服務,可以使用 Spring Cloud 的 DiscoveryClient 類,或者 Eureka 的 EurekaClient 類,Spring Cloud 對 Eureka 進行了封裝。

本例中調用了 DiscoveryClient 類的方法來查詢服務實例。在控制器的 health 方法中,僅將查詢到的服務實例進行輸出。

4.2、測試服務健康狀態
  1. 運行服務器。
  2. 運行服務提供者(數據庫連接狀態默認爲DOWN)。
  3. 運行服務調用者。
  4. 在瀏覽器中輸入http://localhost:9000/health,可以看到只有服務調用者的信息。
    在這裏插入圖片描述
  5. 在瀏覽器中輸入http://localhost:8080/db/true,將數據庫設置爲可以連接。
  6. 在瀏覽器中輸入http://localhost:9000/health,可以看到此時出現了服務提供者的信息。
    在這裏插入圖片描述

根據輸出結果可知,將數據庫設置爲不可連接後,可用的服務只剩下調用者自己,服務提供者已經不存在於服務列表中。

運行案例需要注意,默認情況下,客戶端到服務器端抓取註冊表會有一定的時間間隔,因此在設置數據庫是否可以連接後,訪問“調用者”查看效果時需要稍等一會兒。

二、Eureka 的常用配置

這裏將介紹部分 Eureka 的常用配置。

1、心跳檢測配置

客戶端的實例會向服務器發送週期性的心跳,默認是 30 秒發送一次。

我們可以通過修改客戶端的 eureka.instance.lease-renewal-interval-in-seconds 屬性來改變這個時間。

服務器端接收心跳請求,如果在一定期限內沒有接收到服務實例的心跳,那麼會將該實例從註冊表中清理掉,其他的客戶端將會無法訪問這個實例。這個期限默認值爲 90 秒。
也就是說,服務器 90 秒沒有收到客戶端的心跳,就會將這個實例從列表中清理掉。

我們可以通過修改客戶端的 eureka.instance.lease-expiration-duration-in-seconds 屬性來改變這個時間。

要注意的是,清理註冊表有一個定時器在執行,默認是 60 秒執一次,如果將 lease-expiration-duration-in-seconds 設置爲小於 60 秒,雖然符合刪除實例的條件,但是還沒到 60 秒,這個實例將仍然存在註冊表中 (因爲還沒有執行清理)。

我們可以在服務器端配置 eureka.server.eviction-interval-timer-in-ms 屬性來修改註冊表的清理間隔,注意該單位是毫秒。

需要特別注意,如果開啓了自我保護模式,則實例不會被剔除。

在測試時,爲避免受自我保護模式的影響,建議先關閉自我保護模式,在服務器中配置:eureka.server.enable-self-preservation = false

2、註冊表抓取間隔

在默認情況下,客戶端每隔 30 秒去服務器端抓取註冊表(可用的服務列表),並且將服務器端的註冊表保存到本地緩存中。

我們可以通過修改客戶端的 eureka.client.registry-fetch-interval-seconds 屬性來改變註冊表抓取間隔,但仍然需要考慮性能,改爲哪個值比較合適,需要在性能與實時性方面進行權衡。

3、配置與使用元數據

元數據都會保存在服務器的註冊表中,並且使用簡單的方式與客戶端進行共享。框架自帶的元數據,包括實例 id、主機名稱、IP 地址等。
在正常情況下,自定義元數據不會改變客戶端的行爲,除非客戶端知道這些元數據的含義。

如果我們需要自定義元數據並提供給其他客戶端使用,可以配置 eureka.instance.metadata-map 屬性來指定。

以下配置片段使用了元數據:

eureka:
  instance:
    hostname: localhost
    metadata-map:
      developer-name: swotxu

配置了一個名爲 developer-name 的元數據,值爲 swotxu,使用元數據的一方,可以調用 DiscoveryClient 的方法獲取元數據,代碼如下:

@Autowired
private DiscoveryClient discoveryClient;

@RequestMapping(value = "/metadata", method = RequestMethod.GET)
public String metadata() {
    StringBuilder sb = new StringBuilder();

    List<ServiceInstance> instanceList = discoveryClient.getInstances("HEALTH-HANDLER-INVOKER");
    for (ServiceInstance service : instanceList) {
        sb.append("developer-name: ").append(service.getMetadata().get("developer-name")).append("</br>");
    }
    return sb.toString();
}

在這裏插入圖片描述
使用方式較爲簡單,就不再贅述。

4、自我保護模式

在開發過程中 ,經常可以在 Eureka 的主界面中看到紅色字體的提醒,內容如下:

EMERGENCY! 
EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. 
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

在這裏插入圖片描述

出現該提示意味着 Eureka 進入了自我保護模式。根據前面章節的介紹可知,客戶端會定時發送心跳給服務器端,如果心跳的失敗率超過一定比例,服務會將這些實例保護起來,並不會馬上將其從註冊表中剔除。
此時對於另外的客戶端來說,有可能會拿到一些無法使用的實例,這種情況可能會導致災難的“蔓延”,這些情況可以使用容錯機制予以解決,關於集羣的容錯機制,我們將在後面的內容中講述。

在開發過程中,爲服務器配置 eureka.server.enable-self-preservation 屬性,將值設置爲 false 來關閉自我保護機制。

關閉後再打開 Eureka 主界面,可以看到以下提示信息:

THE SELF PRESERVATION MODE IS TURNED OFF. 
THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

自我保護模式己經關閉,在出現網絡或者其他問題時,將不會保護過期的實例。

在這裏插入圖片描述

三、項目下載

碼雲Gitee倉庫地址:https://gitee.com/swotxu/Spring-Cloud-Study.git >>戳這裏<<
項目路徑:Spring-Cloud-Study/01/springcloud03

四、Eureka 服務小結

通過這三篇的 Eureka 介紹,大家應該可以掌握 Eureka 的架構、如何使用 Eureka 搭建集羣等內容,最基本的是,能對 Spring Cloud 等技術有一個初步認識。

如果想更進一步,可以學習編寫服務實例的自檢程序。實際環境中的服務不可迦免地依賴第三方環境,例如數據庫、第三方服務等,因此,如果掌握了對程序的自檢,可以使得我們的應用程序更加健壯。

下一篇,將介紹 String Cloud 中的負載均衡組件 Ribbon


持續更新中,別忘了點贊關注收藏~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章