1. 程式人生 > 實用技巧 >AlibabaCloud 核⼼元件熔斷降級 Sentinel 實戰

AlibabaCloud 核⼼元件熔斷降級 Sentinel 實戰

高併發下存在的問題

  • 微服務拆分多個系統,服務之間互相依賴,可能會由於系統負載過高,突發流量或者網路等各種異常情況,導致服務不可用。

核心思想——面向失敗程式設計

  • 不要外界影響
  • 不被請求拖垮
    • 上游服務
    • 下游服務

高併發下的微服務容錯方案

  • 限流
    • 漏斗,不管流量多大,均勻的流入容器,令牌桶演算法,漏桶演算法

  • 熔斷:
    • 保險絲,熔斷服務,為了防止整個系統故障,包含當前和下游服務。下單服務 -》商品服務-》使用者服務 -》(出現異常-》熔斷風控服務
  • 降級:
    • 拋棄一些非核心的介面和資料,返回兜底資料。旅行箱的例子:只帶核心的物品,拋棄非核心的,等有條件的時候再去攜帶這些物品
  • 隔離:
    • 服務和資源互相隔離,比如網路資源,機器資源,執行緒資源等,不會因為某個服務的資源不足而搶佔其他服務的資源
  • 熔斷和降級互相交集
    • 相同點:
      • 從可用性和可靠性觸發,為了防止系統崩潰
      • 最終讓使用者體驗到的是某些功能暫時不能用
    • 不同點
      • 服務熔斷一般是下游服務故障導致的,而服務降級一般是從整體系統負荷考慮,由呼叫方控制

想進行微服務的容錯,業界目前有 Sentinel、Hystrix,相對於 AlibabaCloud 而言,Sentinel 是最好的搭配

流量防衛兵Sentinel

什麼是Sentinel

  • 阿里巴巴開源的分散式系統流控工具
  • 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性
  • 豐富的應用場景:訊息削峰填谷、叢集流量控制、實時熔斷下游不可用應用等
  • 完備的實時監控:Sentinel 同時提供實時的監控功能
  • 提供開箱即用的與其它開源框架/庫的整合模組,例如與 Spring Cloud、Dubbo、gRPC 的整合

官網:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

Sentinel 版本:2.2.1

核心概念:

  • 資源:是 Sentinel 中的核心概念之一,可以是java程式中任何內容,可以是服務或者方法甚至程式碼,總結起來就是我們要保護的東西
  • 規則:定義怎樣的方式保護資源,主要包括流控規則、熔斷降級規則等

Sentinel 和控制檯搭建

Sentinel 分為兩個部分

  • 核心庫(Java 客戶端)不依賴任何框架/庫,能夠運行於所有 Java 執行時環境,同時對 Dubbo、Spring Cloud 等框架也有較好的支援。
  • 控制檯(Dashboard)基於 Spring Boot 開發,打包後可以直接執行,不需要額外的 Tomcat 等應用容器。

微服務引入 Sentinel 依賴

 <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

Sentinel 控制檯搭建

  • 文件:https://github.com/alibaba/Sentinel/wiki/控制檯
  • 控制檯包含如下功能:
    • 檢視機器列表以及健康情況:收集 Sentinel 客戶端傳送的心跳包,用於判斷機器是否線上。
    • 監控 (單機和叢集聚合)通過 Sentinel 客戶端暴露的監控 API,定期拉取並且聚合應用監控資訊,最終可以實現秒級的實時監控。
    • 規則管理和推送:統一管理推送規則。
    • 鑑權:生產環境中鑑權非常重要。這裡每個開發者需要根據自己的實際情況進行定製。

注意:Sentinel 控制檯目前僅支援單機部署

//啟動 Sentinel 控制檯需要 JDK 版本為 1.8 及以上版本,
//-Dserver.port=8080 用於指定 Sentinel 控制檯埠為 8080 
//預設使用者名稱和密碼都是 sentinel
​
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar

多個微服務接入 Sentinel 配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080 
        port: 9999 
​
#dashboard: 8080 控制檯埠
#port: 9999 本地啟的埠,隨機選個不能被佔用的(每個啟動的微服務 port 都應該不同),與 dashboard 進行資料互動,會在應用對應的機器上啟動一個 Http Server,該 Server 會與 Sentinel 控制檯做互動, 若被佔用,則開始+1一次掃描

微服務註冊上去後,由於 Sentinel 是懶載入模式,所以需要訪問微服務後才會在控制檯出現

限流配置實操

  • 控制檯配置

  • 瀏覽器重新整理

Sentinel 流量控制功能

流量控制(flow control)

  • 監控應用流量的 QPS 或併發執行緒數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰沖垮,從而保障應用的高可用性。

兩種規則

  • 基於統計併發執行緒數的流量控制
    • 併發數控制用於保護業務執行緒池不被慢呼叫耗盡
    • Sentinel 併發控制不負責建立和管理執行緒池,而是簡單統計當前請求上下文的執行緒數目(正在執行的呼叫數目)
    • 如果超出閾值,新的請求會被立即拒絕,效果類似於訊號量隔離。
  • 基於統計 QPS 的流量控制
    • 當 QPS 超過某個閾值的時候,則採取措施進行流量控制

控制面板介紹

  • 資源名:預設是請求路徑,可自定義
  • 針對來源:對哪個微服務進行限流,預設是不區分來源,全部限流,這個是針對區分上游服務進行限流,比如視訊服務被訂單服務、使用者服務呼叫,就可以針對來源進行限流

基於併發執行緒進行限流配置實操

開發臨時介面,方便測試

@RequestMapping("list")
public Object list(){
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Map<String,String> map  = new HashMap<>();
​
    map.put("title1","ALibabaCloud微服務專題");
    map.put("title2","小滴課堂面試專題第一季");
​
    return map;
}

如果超出閾值,新的請求會被立即拒絕,效果類似於訊號量隔離。併發數控制通常在呼叫端進行配置

流控規則會下發到微服務,微服務如果重啟,則流控規則會消失(可以持久化配置)

選擇閾值型別“執行緒數” ,配置是1

重新整理瀏覽器

流量控制的效果

流量控制的效果包括以下幾種:

  • 直接拒絕:預設的流量控制方式,當 QPS 超過任意規則的閾值後,新的請求就會被立即拒絕
  • Warm Up:冷啟動/預熱,如果系統在此之前長期處於空閒的狀態,我們希望處理請求的數量是緩步的增多,經過預期的時間以後,到達系統處理請求個數的最大值
  • 勻速排隊:嚴格控制請求通過的間隔時間,也即是讓請求以均勻的速度通過,對應的是漏桶演算法,主要用於處理間隔性突發的流量,如訊息佇列,想象一下這樣的場景,在某一秒有大量的請求到來,而接下來的幾秒則處於空閒狀態,我們希望系統能夠在接下來的空閒期間逐漸處理這些請求,而不是在第一秒直接拒絕多餘的請求

注意:

  • 勻速排隊等待策略是 Leaky Bucket 演算法結合虛擬佇列等待機制實現的。
  • 勻速排隊模式暫時不支援 QPS > 1000 的場景

流控文件:https://github.com/alibaba/Sentinel/wiki/流量控制#基於呼叫關係的限流

Sentinel熔斷降級規則

熔斷降級(雖然是兩個概念,基本都是互相配合)

  • 對呼叫鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一
  • 對不穩定的弱依賴服務呼叫進行熔斷降級,暫時切斷不穩定呼叫,避免區域性不穩定因素導致整體的雪崩
  • 熔斷降級作為保護自身的手段,通常在客戶端(呼叫端)進行配置

什麼是 Sentinel 降級規則

Sentinel 熔斷策略

  • 慢呼叫比例(響應時間): 選擇以慢呼叫比例作為閾值,需要設定允許的慢呼叫 RT(即最大的響應時間),請求的響應時間大於該值則統計為慢呼叫
    • 比例閾值:修改後不生效-目前已經反饋給官方那邊的bug
    • 熔斷時長:超過時間後會嘗試恢復
    • 最小請求數:熔斷觸發的最小請求數,請求數小於該值時即使異常比率超出閾值也不會熔斷
  • 異常比例:當單位統計時長內請求數目大於設定的最小請求數目,並且異常的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷
    • 比例閾值
    • 熔斷時長:超過時間後會嘗試恢復
    • 最小請求數:熔斷觸發的最小請求數,請求數小於該值時,即使異常比率超出閾值也不會熔斷
  • 異常數:當單位統計時長內的異常數目超過閾值之後會自動進行熔斷
    • 異常數
    • 熔斷時長:超過時間後會嘗試恢復
    • 最小請求數:熔斷觸發的最小請求數,請求數小於該值時即使異常比率超出閾值也不會熔斷

常見的熔斷狀態和恢復

服務熔斷一般有三種狀態(畫圖)

  • 熔斷關閉(Closed)
    • 服務沒有故障時,熔斷器所處的狀態,對呼叫方的呼叫不做任何限制
  • 熔斷開啟(Open)
    • 後續對該服務介面的呼叫不再經過網路,直接執行本地的 fallback 方法
  • 半熔斷(Half-Open)
    • 所謂半熔斷就是嘗試恢復服務呼叫,允許有限的流量呼叫該服務,並監控呼叫成功率

熔斷恢復:

  • 經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態)嘗試恢復服務呼叫,允許有限的流量呼叫該服務,並監控呼叫成功率。
  • 如果成功率達到預期,則說明服務已恢復,進入熔斷關閉狀態;如果成功率仍舊很低,則重新進入熔斷狀態

服務呼叫熔斷例子

修改程式碼

int temp = 0;

@RequestMapping("list")
public Object list() {
    /*try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }*/
    temp++;
    if (temp % 3 == 0) {
        throw new RuntimeException();
    }
    Map<String, String> map = new HashMap<>();
    map.put("title1", "ALibabaCloud微服務專題");
    map.put("title2", "小滴課堂面試專題第一季");
    return map;
}

熔斷測試

Sentinel自定義異常-整合Open-Feign

預設降級返回資料問題

  • 限流和熔斷返回的資料有問題
  • 微服務互動基本都是 json 格式,需要自定義異常資訊

AlibabCloud 版本升級,不相容問題

  • v2.1.0 到 v2.2.0 後,Sentinel 裡面依賴進行了改動,且不向下相容

自定義降級返回資料

  • 【舊版】實現 UrlBlockHandler 並且重寫 blocked 方法
  • 【新版】實現 BlockExceptionHandler 並且重寫 handle 方法
// 舊版本
@Component
public class XdclassUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
       //降級業務處理
    }
}

//新版本
public class XdclassUrlBlockHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
    //降級業務處理
    }
}

異常種類

  • FlowException //限流異常
  • DegradeException //降級異常
  • ParamFlowException //引數限流異常
  • SystemBlockException //系統負載異常
  • AuthorityException //授權異常

【新版】實現 BlockExceptionHandler 並且重寫 handle 方法

@Component
public class XdclassUrlBlockHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException, IOException {
        Map<String, Object> backMap = new HashMap<>();
        if (e instanceof FlowException) {
            backMap.put("code", -1);
            backMap.put("msg", "限流-異常啦");
        } else if (e instanceof DegradeException) {
            backMap.put("code", -2);
            backMap.put("msg", "降級-異常啦");
        } else if (e instanceof ParamFlowException) {
            backMap.put("code", -3);
            backMap.put("msg", "熱點-異常啦");
        } else if (e instanceof SystemBlockException) {
            backMap.put("code", -4);
            backMap.put("msg", "系統規則-異常啦");
        } else if (e instanceof AuthorityException) {
            backMap.put("code", -5);
            backMap.put("msg", "認證-異常啦");
        }
        // 設定返回json資料
        httpServletResponse.setStatus(200);
        httpServletResponse.setHeader("content-Type", "application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(backMap));
    }
}

Feign整合Sentinel配置

整合步驟

  • 加入依賴
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency
  • 開啟 Feign 對 Sentinel 的支援
# 開啟feign對Sentinel
feign:
  sentinel:
    enabled: true
  • 建立容錯類,實現對應的服務介面,記得加註解 @Service
@Service
public class VideoServiceFallback implements VideoService {
    @Override
    public Video findById(int videoId) {
        Video video = new Video();
        video.setTitle("熔斷降級資料");
        return video;
    }
}
  • 配置 Feign 容錯類
@FeignClient(name = "xdclass-video-service", fallback = VideoServiceFallback.class)
public interface VideoService {
    @GetMapping(value = "/api/v1/video/find_by_id")
    Video findById(@RequestParam("videoId") int videoId);
}
  • 準備兜底資料