SpringCloudAlibaba分散式流量控制元件Sentinel實戰與原始碼分析-中

itxiaoshen發表於2022-06-30

實戰示例

控制檯初體驗

Sentinel的控制檯啟動後,控制檯頁面的內容資料都是空的,接下來我們來逐步操作演示結合控制檯的使用,在上一節也已說明整合SpringCloud Alibaba第一步先加入spring-cloud-starter-alibaba-sentinel啟動器依賴

image-20220618191046621

配置檔案新增引數,dashboard即為控制檯的埠,我們是本地啟動使用8858埠

spring:
  cloud:
    sentinel:
      enabled: true
      transport:
        dashboard: localhost:8858
        port: 8719

訂單新增控制器中有一個訂單新增的介面

image-20220618205619408

啟動訂單微服務程式,訪問訂單控制器的新增訂單介面,http://localhost:4070/add-order

image-20220618205556580

然後再檢視sentinel的控制檯首頁-簇點鏈路,這時已經有/add-order這個資源名的資料

image-20220618205402824

多次訪問後在實時監控也可以看到相應資源的實時QPS統計資料

image-20220619114440624

  • Sentinel 的所有規則都可以在記憶體態中動態地查詢及修改,修改之後立即生效。同時 Sentinel 也提供相關 API來定製自己的規則策略。
  • Sentinel 支援以下幾種規則:流量控制規則熔斷降級規則系統保護規則來源訪問控制規則熱點引數規則
  • 流控規則

定義

  • 流量控制:Sentinel的流控的原理主要是監控應用流量或者說是資源的QPS或者併發執行緒數,當達到指定的閾值後對流量進行控制,以避免被瞬時的流量洪峰沖垮,從而保障應用的高可用性

  • QPS:每秒請求數,即在不斷向伺服器傳送請求的情況下,伺服器每秒能夠處理的請求數量。

  • 併發執行緒數:指的是施壓機施加的同時請求的執行緒數量。

image-20220619114718764

流量規則的定義的重要屬性:

Field 說明 預設值
resource 資源名,資源名是限流規則的作用物件
count 限流閾值
grade 限流閾值型別,QPS 或執行緒數模式 QPS 模式
limitApp 流控針對的呼叫來源 default,代表不區分呼叫來源
strategy 呼叫關係限流策略:直接、鏈路、關聯 根據資源本身(直接)
controlBehavior 流控效果(直接拒絕 / 排隊等待 / 慢啟動模式),不支援按呼叫關係限流 直接拒絕

FlowSlot 會根據預設的規則,結合前面 NodeSelectorSlotClusterNodeBuilderSlotStatistcSlot 統計出來的實時資訊進行流量控制。同一個資源可以對應多條限流規則。FlowSlot 會對該資源的所有限流規則依次遍歷,直到有規則觸發限流或者所有規則遍歷完畢。一條限流規則主要由下面幾個因素組成,可以組合這些元素來實現不同的限流效果:

  • resource:資源名,即限流規則的作用物件
  • count: 限流閾值
  • grade: 限流閾值型別,QPS 或執行緒數
  • strategy: 根據呼叫關係選擇策略

流控型別

基於QPS流控

通過控制檯首頁-簇點鏈路,在相應資源的記錄右邊點選流控按鈕並設定相應的流控規則。

image-20220619120807538

快速訪問訂單控制器的新增訂單介面,當前設定每秒超過2次就會被流控

image-20220619120935239

同樣我們也可以和前面核心庫示例一樣自定流控提示

image-20220619122257455

設定訂單查詢介面的流控規則

image-20220619121805583

通過控制檯首頁-流控規則檢視當前的流控規則,每次重啟微服務模組其設定規則在記憶體中丟失了,也即是前面設定新增訂單的流控規則也沒有了,如需要則需重新設定

image-20220619122521276

快速訪問訂單控制器的查詢訂單介面

image-20220619122207341

併發執行緒數

執行緒數限流用於保護業務執行緒數不被耗盡。例如,當應用所依賴的下游應用由於某種原因導致服務不穩定、響應延遲增加,對於呼叫者來說,意味著吞吐量下降和更多的執行緒數佔用,極端情況下甚至導致執行緒池耗盡。為應對高執行緒佔用的情況,業內有使用隔離的方案,比如通過不同業務邏輯使用不同執行緒池來隔離業務自身之間的資源爭搶(執行緒池隔離),或者使用訊號量來控制同時請求的個數(訊號量隔離)。這種隔離方案雖然能夠控制執行緒數量,但無法控制請求排隊時間。當請求過多時排隊也是無益的,直接拒絕能夠迅速降低系統壓力。Sentinel執行緒數限流不負責建立和管理執行緒池,而是簡單統計當前請求上下文的執行緒個數,如果超出閾值,新的請求會被立即拒絕。我們在查詢訂單介面中增加休眠來演示效果

    @RequestMapping("/query-order")
    @SentinelResource(value = "query-order",blockHandler = "querydOrderBlockHandler")
    public String querydOrder() {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "訂單查詢成功";
    }

設定併發執行緒數的閾值為1

image-20220619164933813

訪問第一個頁面的緊跟著再開啟另外一個頁面繼續訪問

image-20220619165214140

第二個頁面就顯示被流控了

image-20220619165305512

流控模式

模式分類

  • 直接拒絕:介面達到限流條件時,直接限流
  • 關聯:當關聯的資源達到閾值時,就限流自己
  • 鏈路:只記錄指定鏈路上的流量(指定資源從入口資源進來的流量,如果達到閾值,就可以限流)

直接拒絕

預設的流量控制方式,當QPS超過任意規則的閾值後,新的請求就會被立即拒絕,拒絕方式為丟擲。前面的例子都是使用直接拒絕,這裡就不再說明。

關聯流控

具有關係的資源流量控制,當兩個資源之間具有資源爭搶或者依賴關係的時候,這兩個資源便具有了關聯。比如對資料庫同一個欄位的讀操作和寫操作存在爭搶,讀的速度過高會影響寫得速度,寫的速度過高會影響讀的速度。如果放任讀寫操作爭搶資源,則爭搶本身帶來的開銷會降低整體的吞吐量。可使用關聯限流來避免具有關聯關係的資源之間過度的爭搶,舉例來說,read_dbwrite_db 這兩個資源分別代表資料庫讀寫,我們可以給 read_db 設定限流規則來達到寫優先的目的:設定 FlowRule.strategyRuleConstant.RELATE 同時設定 FlowRule.ref_identitywrite_db。這樣當寫庫操作過於頻繁時,讀資料的請求會被限流。我們先把前面的querydOrder休眠去掉,設定流控模式為關聯,關聯資源為新增訂單介面

image-20220619171355435

先通過ApiFox設定迴圈訪問,也可以通過其他工具如jmeter等,ApiFox也可以前面的文章也有講解去使用

image-20220619172046069

啟動持續的訪問訂單查詢介面

image-20220619172004636

訪問訂單新增已經顯示被流控了,暫停ApiFox後訪問則正常。

image-20220619171732276

鏈路流控

NodeSelectorSlot 中記錄了資源之間的呼叫鏈路,這些資源通過呼叫關係,相互之間構成一棵呼叫樹。這棵樹的根節點是一個名字為 machine-root 的虛擬節點,呼叫鏈的入口都是這個虛節點的子節點。在訂單實現類中增加測試方法

    @Override
    @SentinelResource(value = "getOrder")
    public String getOrder() {
        return "測試查詢訂單";
    }

訂單控制器增加兩個介面方法,都呼叫到了getOrder

    @RequestMapping("/test1")
    public String test1(){
        return orderService.getOrder();
    }

    @RequestMapping("/test2")
    public String test2(){
        return orderService.getOrder();
    }

配置檔案增加下面引數

spring:
  cloud:
    sentinel:
      web-context-unify: false

啟動程式為getOrder資源設定鏈路流控模式,入口資源為/test2

image-20220619180937066

快速訪問http://localhost:4070/test2 ,出現被流控了,而快速訪問http://localhost:4070/test1則都是正常請求

流控效果

快速失敗

預設的流量控制方式,當QPS超過任意規則的閾值後,新的請求就會被立即拒絕,拒絕方式為丟擲FlowException。前面大部分例子都是使用了快速失敗演示。

Warm Up

  • Sentinel的Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即預熱/冷啟動方式。當系統長期處於低水位的情況下,當流量突然增加時,直接把系統拉昇到高水位可能瞬間把系統壓垮。通過"冷啟動",讓通過的流量緩慢增加,讓伺服器一點一點處理,再慢慢加量,在一定時間內逐漸增加到閾值上限,給冷系統一個預熱的時間,避免冷系統被壓垮。
  • 預熱底層是根據令牌桶演算法實現的。
  • warm up冷啟動主要用於啟動需要額外開銷的場景,例如建立資料庫連線。設定QPS閾值為3,流控效果為warm up,預熱時長為5秒,這樣配置之後有什麼效果呢:QPS起初會從(3/3/=1)每秒通過一次請求開始預熱直到5秒之後達到每秒通過3次請求;前幾秒是頻繁流控的,直到5秒,QPS閾值達到了3。

設定相應流控規則,流控效果選擇Warm Up

image-20220619183424687

有激增的流量持續請求訂單查詢介面http://localhost:4070/query-order,檢視實時監控資料,有一個慢慢預熱的過程

image-20220619185126010

勻速排隊

  • 勻速排隊方式會嚴格控制請求通過的間隔時間,也即是讓請求以均勻的速度通過,對應的是漏桶演算法。有個超時等待時間,一旦超過這個預定設定的時間將會被限流。
  • 適合用於請求以突刺狀來到,這個時候我們不希望一下子把所有的請求都通過,這樣可能會把系統壓垮;同時我們也期待系統以穩定的速度,逐步處理這些請求,以起到“削峰填谷”的效果,而不是拒絕所有請求。

熔斷降級

概述

除了流量控制以外,對呼叫鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。一個服務常常會呼叫別的模組,可能是另外的一個遠端服務、資料庫,或者第三方 API 等。例如,支付的時候,可能需要遠端呼叫銀聯提供的 API;查詢某個商品的價格,可能需要進行資料庫查詢。然而,這個被依賴服務的穩定性是不能保證的。如果依賴的服務出現了不穩定的情況,請求的響應時間變長,那麼呼叫服務的方法的響應時間也會變長,執行緒會產生堆積,最終可能耗盡業務自身的執行緒池,服務本身也變得不可用。

image-20220619223446567

現代微服務架構都是分散式的,由非常多的服務組成。不同服務之間相互呼叫,組成複雜的呼叫鏈路。以上的問題在鏈路呼叫中會產生放大的效果。複雜鏈路上的某一環不穩定,就可能會層層級聯,最終導致整個鏈路都不可用。因此我們需要對不穩定的弱依賴服務呼叫進行熔斷降級,暫時切斷不穩定呼叫,避免區域性不穩定因素導致整體的雪崩。熔斷降級作為保護自身的手段,通常在客戶端(呼叫端)進行配置。

熔斷策略

Sentinel 提供以下幾種熔斷策略:

  • 慢呼叫比例 (SLOW_REQUEST_RATIO):選擇以慢呼叫比例作為閾值,需要設定允許的慢呼叫 RT(即最大的響應時間),請求的響應時間大於該值則統計為慢呼叫。當單位統計時長(statIntervalMs)內請求數目大於設定的最小請求數目,並且慢呼叫的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求響應時間小於設定的慢呼叫 RT 則結束熔斷,若大於設定的慢呼叫 RT 則會再次被熔斷。
  • 異常比例 (ERROR_RATIO):當單位統計時長(statIntervalMs)內請求數目大於設定的最小請求數目,並且異常的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。異常比率的閾值範圍是 [0.0, 1.0],代表 0% - 100%。
  • 異常數 (ERROR_COUNT):當單位統計時長內的異常數目超過閾值之後會自動進行熔斷。經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。

慢呼叫比例

在新增訂單介面休眠兩秒來演示image-20220619224727504

簇點鏈路點選熔斷按鈕進行設定

image-20220619230234001

先用用apifox多個執行緒呼叫http://localhost:4070/add-order

image-20220619230105790

再訪問http://localhost:4070/add-order,顯示當前請求被流量限制了

image-20220619230039464

異常比例

在新增訂單介面中新增異常程式碼

image-20220619230455097

設定規則

image-20220619230754622

同樣先用apifox多個執行緒呼叫http://localhost:4070/add-order,然後再訪問http://localhost:4070/add-order,顯示當前請求被流量限制了

image-20220619230834094

同樣異常數的設定也是如此

image-20220619231108675

熱點引數限流

  • 熱點即經常訪問的資料。很多時候我們希望統計某個熱點資料中訪問頻次最高的 Top K 資料,並對其訪問進行限制。比如商品 ID 為引數,統計一段時間內最常購買的商品 ID 並進行限制。
  • 熱點引數限流會統計傳入引數中的熱點引數,並根據配置的限流閾值與模式,對包含熱點引數的資源呼叫進行限流。熱點引數限流可以看做是一種特殊的流量控制,僅對包含熱點引數的資源呼叫生效。
  • Sentinel 利用 LRU 策略統計最近最常訪問的熱點引數,結合令牌桶演算法來進行引數級別的流控。

熱點引數規則(ParamFlowRule)類似於前面列出流量控制規則(FlowRule),詳細可以查閱官網

建立測試方法,帶路徑變數引數

    @RequestMapping("/get/{id}")
    public String getByOrderId(@PathVariable("id") Integer id){
        log.info("getByOrderId id={}",id);
        return "查詢訂單正常";
    }

啟動程式,訪問http://localhost:4070/order/get/1

image-20220630134422085

設定熱點規則

image-20220630133831182

點選編輯進入設定高階選項,增加引數例外項,點選新增

image-20220630133950500

訪問http://localhost:4070/order/get/2 則可以正常,不受限流閾值2的限制,而連續訪問http://localhost:4070/order/get/1 則出現限流

image-20220630134019976

統一異常處理

統一異常處理適合對BlockException返回的資訊處理是一樣的,如果不一樣則還是需要使用@SentinelResource

建立統一返回實體類Result

package cn.itxs.ecom.commons.entity;

public class Result<T> {
    private Integer code;
    private String msg;
    private T data;

    public Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static Result error(Integer code,String msg){
        return new Result(code,msg);
    }
}

新增ItxsBlockExceptionHandler.java

package cn.itxs.ecom.order.exception;

import cn.itxs.ecom.commons.entity.Result;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
@Slf4j
public class ItxsBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
        //getRule返回資源、規則的詳細資訊
        log.info("BlockExceptionHandler BlockException================"+e.getRule());

        Result r = null;
        if(e instanceof FlowException){
            r = Result.error(400,"哈哈哈,統一處理方法處介面被限流了");
        }else if (e instanceof DegradeException){
            r = Result.error(401,"哈哈哈,統一處理方法處服務降級了");
        }else if (e instanceof ParamFlowException){
            r = Result.error(402,"哈哈哈,統一處理方法處熱點引數限流了");
        }else if (e instanceof AuthorityException){
            r = Result.error(404,"哈哈哈,統一處理方法處理授權規則不通過");
        }else if (e instanceof SystemBlockException){
            r = Result.error(405,"哈哈哈,統一處理方法處理系統規則不通過");
        }

        //返回Json資料
        response.setStatus(200);
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer=null;
        try {
            writer=response.getWriter();
            writer.write(JSON.toJSONString(r));
            writer.flush();
        } catch (IOException ioException) {
            log.error("異常:{}",ioException);
        }finally {
            if(writer!=null) {
                writer.close();
            }
        }
    }
}

測試的控制器則無需使用@SentinelResource

image-20220629174052899

啟動訪問http://localhost:4070/order/add ,然後設定流控規則,再次訪問,這是則是統一異常返回結果,配置熔斷降級規則命中也是如此。

image-20220629174256893

系統規則限流

系統保護規則是從應用級別的入口流量進行控制,從單臺機器的總體 Load、RT、入口 QPS 和執行緒數四個維度監控應用資料,讓系統儘可能跑在最大吞吐量的同時保證系統整體的穩定性。系統保護規則是應用整體維度的,而不是資源維度的,並且僅對入口流量生效。入口流量指的是進入應用的流量(EntryType.IN),比如 Web 服務或 Dubbo 服務端接收的請求,都屬於入口流量。在系統規則中設定閾值型別CPU ,閾值為0.1

image-20220630140738106

由於我本機CPU一直高於10%的,訪問http://localhost:4070/order/add 後會出現系統規則限流了,而調高CPU的閾值如0.8後訪問則是正常的。

image-20220630140703831

整合OpenFeign使用

準備一個庫存微服務,前面我們已經使用,在庫存控制器增加一個測試方法和啟動庫存微服務

    @RequestMapping("/deduct-storage")
    public String deductStorage(){
        int i = 1/0;
        return "扣減庫存";
    }

image-20220630162826515

訂單微服務中增加一個StorageFeignService的Feign介面宣告

package cn.itxs.ecom.order.service;

import cn.itxs.ecom.commons.service.openfeign.StorageFeignServiceFackBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(value = "ecom-storage-service",fallback = StorageFeignServiceFackBack.class)
public interface StorageFeignService {
    @RequestMapping("/deduct-storage")
    String deductStorage();
}

建立降級的實現類

package cn.itxs.ecom.order.service;
import cn.itxs.ecom.commons.service.openfeign.StorageFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class StorageFeignServiceFackBack implements StorageFeignService {
    @Override
    public String deductStorage() {
        log.info("進入補償處理的流程----------");
        return "進入補償處理的流程----------";
    }
}

訂單控制器增加方法

    @Autowired
    OrderService orderService;    

	@RequestMapping("/create-order")
    public String createOrder(){
        return orderService.createOrder();
    }

訂單服務介面的實現類呼叫宣告庫存的Feign介面

package cn.itxs.ecom.order.service.impl;

import cn.itxs.ecom.commons.service.OrderService;
import cn.itxs.ecom.commons.service.openfeign.StorageFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private StorageFeignService storageFeignService;

    @Override
    public String createOrder() {
        return storageFeignService.deductStorage();
    }
}

啟動配置檔案增加啟用feign整合sentinel

feign:
  sentinel:
    enabled: true

在訂單微服務的啟動類增加@EnableFeignClients開啟

image-20220630163145125

訪問http://localhost:4070/order/create-order,觸發庫存服務異常後返回補償流程提示。

image-20220630163405344

規則持久化

DataSource 擴充套件常見的實現方式有:

  • 拉模式:客戶端主動向某個規則管理中心定期輪詢拉取規則,這個規則中心可以是 RDBMS、檔案,甚至是 VCS 等。這樣做的方式是簡單,缺點是無法及時獲取變更;
  • 推模式:規則中心統一推送,客戶端通過註冊監聽器的方式時刻監聽變化,比如使用Nacos、Zookeeper 等配置中心。這種方式有更好的實時性和一致性保證。

Sentinel 目前支援以下資料來源擴充套件:

  • Pull-based: 動態檔案資料來源、Consul, Eureka
  • Push-based: ZooKeeper, Redis, Nacos, Apollo, etcd

Dashboard中新增的規則資料儲存在記憶體,微服務停掉規則資料就消失,在⽣產環境下不合適。我們可以將Sentinel規則資料持久化到Nacos配置中⼼,讓微服務從Nacos獲取規則資料。

新增依賴

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

Nacos增加配置

image-20220630170701374

啟動配置檔案中增加

spring:
  application:
    name: ecom-order-service
  cloud:
    sentinel:
      enabled: true
      transport:
        dashboard: localhost:8858
        port: 8719
      datasource:
        # 此處的flow為⾃定義資料來源名
        flow: # 流控規則
          nacos:
            # server-addr: ${spring.cloud.nacos.discovery.server-addr}
            server-addr: ${spring.cloud.nacos.server-addr}
            namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
            data-id: ${spring.application.name}-flow-rules
            groupId: order-group
            username: itsx
            password: itxs123
            data-type: json
            rule-type: flow # 型別來⾃RuleType類

快速訪問http://localhost:4070/order/add,出現被流控的提示

image-20220630171012769

檢視sentinel控制檯流控規則也是我們在Nacos上的流控規則配置

image-20220630171101060

**本人部落格網站 **IT小神 www.itxiaoshen.com

相關文章