Sentinel-流量防衛兵

2022-07-05 18:00:38

1.背景

1.1 簡介

Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。

Sentinel 具有以下特徵

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、訊息削峰填谷、叢集流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制檯中看到接入應用的單臺機器秒級資料,甚至 500 臺以下規模的叢集的彙總執行情況。
  • 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模組,例如與 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相應的依賴並進行簡單的設定即可快速地接入 Sentinel。同時 Sentinel 提供 Java/Go/C++ 等多語言的原生實現。
  • 完善的 SPI 擴充套件機制:Sentinel 提供簡單易用、完善的 SPI 擴充套件介面。您可以通過實現擴充套件介面來快速地客製化邏輯。例如客製化規則管理、適配動態資料來源等。

Sentinel 的主要特性

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

1.2 學習參考

1.3 章節介紹

本文主要介紹以下知識點:
  • 基於Spring boot 對接Sentinel;
  • Nacos設定Sentinel規則資訊;
  • 測試流控規則,系統保護規則,熔斷規則;
  • Sentinel控制檯資料展示問題;
  • Nacos規則儲存與Sentinel修改資料同步問題;
  • Sentinel責任鏈模式分析

2.專案構建

2.1 pom設定

<!--        nacos設定-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <!--        sentinel設定-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <!--        sentinel 規則基於nacos儲存-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.3</version>
        </dependency>

2.2 專案引數設定

server:
  servlet:
    context-path: /sentinel-nacos-demo
spring:
  application:
    name: sentinel-nacos-demo
  profiles:
    active: local
  cloud:
    nacos:
      config:
        server-addr: xxx.xxx.xx.x:8848
        #server-addr: xxx.xxx.xx.x:8848
group: ${spring.application.name}
        file-extension: yaml
        # 設定中心使用單獨namespace
namespace: "study"
      discovery:
        server-addr: xxx.xxx.xx.x:8848
        namespace: "study"
        group: "sentinel-nocas-demo"
    sentinel:
      transport:
        dashboard: xxx.xxx.xx.x:8842  #啟動本專案後需要請求一次才能向sentinel控制檯註冊
port: 8719  #當一個伺服器部署多個應用時要設定不同port,單個應用可忽略
client-ip: 10.32.4.230   #指定本機ip地址,避免多個虛擬地址,導致資料獲取失敗
datasource:
        ## 設定流程控制
        ## rule-type 設定表示該資料來源中的規則屬於哪種型別的規則(flow流控,degrade熔斷降級,authority授權,system系統保護, param-flow熱點引數限流, gw-flow, gw-api-group)
flow:
          nacos:
            server-addr: xxx.xxx.xx.x:8848
            namespace: "study"
            data-id: ${spring.application.name}-sentinel-flow-rules
            group-id: sentinel-group
            data-type: json
            rule-type: flow
              ## 設定降級規則
degrade:
          nacos:
            server-addr: xxx.xxx.xx.x:8848
            namespace: "study"
            dataId: ${spring.application.name}-sentinel-degrade-rules
            groupId: sentinel-group
            data-type: json
            rule-type: degrade
        system:
          nacos:
            server-addr: xxx.xxx.xx.x:8848
            namespace: "study"
            dataId: ${spring.application.name}-sentinel-system-rules
            groupId: sentinel-group
            data-type: json
            rule-type: system

2.3 規則設定

在Nacos設定中心中設定如下設定:

Sentinel 流控規則設定

[
    {
        "resource": "/sentinel/rule/flow",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

Sentinel 熔斷規則設定

[
    {
        "resource": "/sentinel/rule/degrade",
        "count": 1,
        "grade": 0,
        "timeWindow": 10,
        "minRequestAmount": 1,
        "statIntervalMs": 1000,
        "slowRatioThreshold": 0.1
    }
]

Sentinel 系統保護規則設定

[
    {
        "avgRt":1,
        "highestCpuUsage":-1,
        "highestSystemLoad":-1,
        "maxThread":-1,
        "qps":1000
    }
]

2.4 規則統一攔截

@Component
public static class MyBlockExceptionHandler implements BlockExceptionHandler {
  @Override
  public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
    //Sentinel規則的詳細資訊
    BaseResponse r = BaseResponse.error("sentinel-控制攔截");
    if (e instanceof FlowException) {
      r = BaseResponse.error("介面限流了",e.toString());
    } else if (e instanceof DegradeException) {
      r = BaseResponse.error( "服務降級了",e.toString());
    } else if (e instanceof ParamFlowException) {
      r = BaseResponse.error("熱點引數限流了",e.toString());
    } else if (e instanceof SystemBlockException) {
      r = BaseResponse.error( "觸發系統保護規則了",e.toString());
    } else if (e instanceof AuthorityException) {
      r = BaseResponse.error( "授權規則不通過",e.toString());
    }
    //返回json資料
    response.setStatus(500);
    response.setCharacterEncoding("utf-8");
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    new ObjectMapper().writeValue(response.getWriter(), r);
  }
}

3.專案執行範例

3.1 規則攔截測試

Sentinel 流控規則測試

Sentinel 熔斷規則測試

Sentinel 系統保護規則測試

3.2 Sentinel 控制檯介面展示

實時監控

簇點鏈路

流控規則

4.控制檯資料展示問題

4.1 簇點鏈路資料為空

造成原因:部署微服務的伺服器存在多個虛擬的ip地址,Sentinel控制檯識別了其中一個的ip地址,但是該地址與控制檯網路不通。
解決辦法(如下兩個方法都可以):
1.部署伺服器設定一個固定的ip地址;
2.設定固定的使用者端ip地址,如:
sentinel:
  transport:
    dashboard: xxx.168.16.13:8842  #啟動本專案後需要請求一次才能向sentinel控制檯註冊
port: 8719  #當一個伺服器部署多個應用時要設定不同port,單個應用可忽略
client-ip: xx.xx.4.230   #指定本機ip地址,避免多個虛擬地址,導致資料獲取失敗

4.2 實時監控資料為空

造成原因:部署維服務的伺服器時間與Sentinel控制檯所在伺服器的時間不一致。
解決辦法:調整兩邊伺服器的時間,在差距為20秒以內, 資料可展示。

5.Sentinel控制檯與Nacos設定中心資料一致性問題

Sentinel控制檯可以通過簇點鏈路設定各種規則,但是規則資訊不能落地儲存。一旦Sentienl服務重啟後,規則就會丟失。
解決方案可以將規則資訊儲存在Nacos中,這樣就可以儲存規則資訊了。目前版本是在Nacos中設定規則資訊後,可以在Sentinel控制檯中檢視,但在Sentinel控制檯修改規則後,不能同步到Nacos中。
針對這樣的情況,解決方案參考如下:
  • 專案組統一規定,規則資訊只能基於Nacos設定,在Nacos中做修改調整,不可在Sentinel控制檯操作規則資訊。
個人建議:目前可以先基於Nacos設定統一管理,後續版本應該會支援雙向同步,非必要情況下,不必造輪子處理。

6.Sentinel部分核心原始碼分析

本文分析原始碼版本:sentinel-core-1.8.1
Sentinel 將 ProcessorSlot 作為 SPI 介面進行擴,使得 Slot Chain 具備了擴充套件的能力。開發人員可以自行加入自定義的 slot 並編排 slot 間的執行順序,從而可以給 Sentinel 新增自定義的功能。

6.1 預設slot執行順序

slot實現類關係圖
NodeSelectorSlot 的實現類呼叫關係:
1.定義實現類載入順序
@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)

2.定義NodeSelectorSlot實現類繼承自抽象類
 public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> 

3.定義抽象類實現介面
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T>

4.定義鏈路頂層介面
public interface ProcessorSlot<T> 

6.2 構建預設責任鏈

載入實現了抽象類AbstractLinkedProcessorSlot的鏈路,若擴充套件時只實現ProcessorSlot 介面,是不能加入到責任鏈路中的。參考原始碼:
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();

        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }

            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}
歷史呼叫鏈組裝邏輯:偵錯程式碼時,發現新版本已經廢棄這種呼叫組裝邏輯了。(責任鏈模式,結合order順序的模式,便於調整和控制)
public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot<Object> {

    /**
     * Add a processor to the head of this slot chain.
     *
     * @param protocolProcessor processor to be added.
     */
public abstract void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor);

    /**
     * Add a processor to the tail of this slot chain.
     *
     * @param protocolProcessor processor to be added.
     */
public abstract void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor);
}

6.3 流程總結

  • 採用責任鏈模式完成Sentinel的資訊統計、熔斷、限流等操作;
  • 責任鏈中NodeSelectSlot負責選擇當前資源對應的Node,同時構建node呼叫樹;
  • 責任鏈中ClusterBuilderSlot負責構建當前Node對應的ClusterNode,用於聚合同一資源對應不同Context的Node;
  • 責任鏈中的StatisticSlot用於統計當前資源的呼叫情況,更新Node與其對用的ClusterNode的各種統計資料;
  • 責任鏈中的FlowSlot根據當前Node對應的ClusterNode(預設)的統計資訊進行限流;
  • 資源呼叫統計資料(例如PassQps)使用滑動時間視窗進行統計;
  • 所有工作執行完畢後,執行退出流程,補充一些統計資料,清理Context。

6.4 編寫一個自定義攔截Slot

/**
 * 編寫一個自定義限流鏈路
 *
 * @author wangling
 * @date 2022/07/05
 */
@Spi(order = -3000)
public class TestMySlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        try {
            fireEntry(context, resourceWrapper, obj, count, prioritized, args);
            throw new BusinessException("TestMySlot-測試");
        } catch (Exception e) {
            throw e;
        } catch (Throwable e) {
            RecordLog.warn("Unexpected entry exception", e);
        }

    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        try {
            fireExit(context, resourceWrapper, count, args);
        } catch (Throwable e) {
            RecordLog.warn("Unexpected entry exit exception", e);
        }
    }
}
設定SPI自動掃描