SpringCloudAlibaba - 分佈式流量防衛兵Sentinel

一. Sentinel: 分佈式系統的流量防衛兵 - 阿里巴巴產品

具體介紹可以看官方文檔:https://github.com/alibaba/Sentinel/wiki/介紹,下面我們說點官方沒有的東西:

服務保護的基本概念:

服務限流/熔斷

服務限流目的是爲了更好的保護我們的服務,在高併發的情況下,如果客戶端請求的數量達到一定極限(後臺可以配置閾值),請求的數量超出了設置的閾值,開啓自我的保護,直接調用我們的服務降級的方法,不會執行業務邏輯操作,直接走本地falback的方法,返回一個友好的提示。

【服務降級】

在高併發的情況下, 防止用戶一直等待,採用限流/熔斷方法,使用服務降級的方式返回一個友好的提示給客戶端,不會執行業務邏輯請求,直接走本地的falback的方法。提示語:當前排隊人數過多,稍後重試~

【服務雪崩】

默認的情況下,Tomcat或者是Jetty服務器只有一個線程池去處理客戶端的請求,這樣的話就是在高併發的情況下,如果客戶端所有的請求都堆積到同一個服務接口上, 那麼就會產生tomcat服務器所有的線程都在處理該接口,可能會導致其他的接口無法訪問。

【服務隔離機制】

線程池隔離機制:每個服務接口都有自己獨立的線程池,互不影響,缺點就是佔用cpu資源非常大。
信號量隔離機制:最多隻有一定的閾值線程數處理我們的請求,超過該閾值會拒絕請求。

  Sentinel Hystrix
隔離策略 基於併發數 線程池隔離/信號量隔離
熔斷降級策略 基於響應時間或失敗比率 基於失敗比率
實時指標實現 滑動窗口 滑動窗口(基於 RxJava)
規則配置 支持多種數據源 支持多種數據源
擴展性 多個擴展點 插件的形式
基於註解的支持 即將發佈 支持
調用鏈路信息 支持同步調用 不支持
限流 基於 QPS / 併發數,支持基於調用關係的限流 不支持
流量整形 支持慢啓動、勻速器模式 不支持
系統負載保護 支持 不支持
實時監控 API 各式各樣 較爲簡單
控制檯 開箱即用,可配置規則、查看秒級監控、機器發現等 不完善
常見框架的適配 Servlet、Spring Cloud、Dubbo、gRPC 等 Servlet、Spring Cloud Netflix

二. SpringBoot整合Sentinel

1. 手動代碼配置

@RestController
public class OrderService {

    private static final String GETORDER_KEY = "getOrder";

    // 手動配置管理Api限流接口
    @RequestMapping("/initFlowQpsRule")
    public String initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GETORDER_KEY);
        // QPS控制在2以內
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        return "....限流配置初始化成功..";
    }
    @RequestMapping("/getOrder")
    public String getOrders() {
        Entry entry = null;
        try {
            entry = SphU.entry(GETORDER_KEY);
            // 執行我們服務需要保護的業務邏輯(省略)
            return "getOrder接口";
        } catch (Exception e) {
            e.printStackTrace();
            return "流量已達上限.";
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
}

啓動項目,首先執行initFlowQpsRule接口;由於我們配置的是QPS(QueriesPerSecond意思是“每秒查詢率”,是一臺服務器每秒能夠相應的查詢次數)爲1,所以一秒內超過一次請求,則走降級,即會執行catch的返回結果。(下圖演示效果爲一秒內刷新兩次):

    

那麼問題來了,每次重啓都需要執行initFlowQpsRule接口,太繁瑣了,那麼我們可以加入到啓動加載類中:

@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
    private static final String GETORDER_KEY = "getOrder";

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GETORDER_KEY);
        // QPS控制在2以內
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        log.info(">>>限流服務接口配置加載成功>>>");
    }
}

2. 註解形式配置管理Api限流

該方式也必須先執行initFlowQpsRule或者加入啓動配置類,只是接口中不用Entry和try catch了:

@RestController
public class OrderService {

    @RequestMapping("/initFlowQpsRule")
    public String initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource("getOrderAnnotation");
        // QPS控制在2以內
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        return "限流配置初始化成功..";
    }


    @SentinelResource(value = "getOrderAnnotation", blockHandler = "getOrderQpsException")
    @RequestMapping("/getOrderAnnotation")
    public String getOrderAnnotation() {
        return "getOrderAnnotation接口";
    }
    /**
     * 被限流後返回的提示
     */
    public String getOrderQpsException(BlockException e) {
        e.printStackTrace();
        return "該接口已經被限流啦!";
    }

}

 該效果和方式1效果一樣,一秒內連續請求兩次getOrderAnnotation接口,則會走限流回調方法getOrderQpsException。

3. Sentinel控制檯管理限流接口

  ① 控制檯環境搭建

下載對應Sentinel-Dashboardhttps://github.com/alibaba/Sentinel/releases/tag/1.7.1    下載後執行命令:

java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\MyTools\sentinel-dashboard-1.6.2.jar

啓動後訪問:http://localhost:8718,默認用戶名和密碼都爲sentinel,登錄成功頁面如下:

② SpringBoot接入Sentinel控制檯

spring:
  application:
    # 服務名稱
    name: service-order
  cloud:
    nacos:
      discovery:
        # nacos註冊地址
        server-addr: 127.0.0.1:8848
      config:
        # 配置中心連接地址
        server-addr: 127.0.0.1:8848
        # 分組
        group: DEFAULT_GROUP
        # 類型
        file-extension: yaml
    sentinel:
      transport:
        dashboard: 127.0.0.1:8718
      eager: true

  服務加入身sentinel配置後,重啓可以看到sentinel控制檯多出了一個應用:

  新寫一個測試接口:

@RestController
public class OrderService {

    @SentinelResource(value = "getOrderByConsole", blockHandler = "getOrderQpsException")
    @RequestMapping("/getOrderByConsole")
    public String getOrderByConsole() {
        return "getOrderByConsole接口";
    }
    public String getOrderQpsException(BlockException e) {
        e.printStackTrace();
        return "該接口已經被限流啦!";
    }
}

  控制檯需要配置資源名稱getOrderByConsole:點擊流控規則 → 新增流控規則:

三. Sentinel持久化

默認的情況下Sentinel的規則是存放在內存中,如果Sentinel客戶端重啓後,Sentinel數據規則可能會丟失。
Sentinel持久化機制支持四種持久化的機制:(這裏我們只講整合Nacos)
1. 本地文件
2. 攜程阿波羅
3. Nacos
4. Zookeeper

 1. 首先,在Nacos配置中心創建流控規則:(注意resource不要加/)


resource:資源名,即限流規則的作用對象

limitApp:流控針對的調用來源,若爲 default 則不區分調用來源

grade:限流閾值類型(QPS 或併發線程數);0代表根據併發數量來限流,1代表根據QPS來進行流量控制

count:限流閾值

strategy:調用關係限流策略

controlBehavior:流量控制效果(直接拒絕、Warm Up、勻速排隊)

clusterMode:是否爲集羣模式


2. 修改訂單服務sentinel配置如下:

   引入依賴並新增測試接口:

<!--sentinel 整合nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.5.2</version>
</dependency>
@SentinelResource(value = "getOrderSentinel", blockHandler = "getOrderSentinelBlockHandler")
@RequestMapping("/getOrderSentinel")
public String getOrderSentinel() {
    return "getOrderSentinel";
}

public String getOrderSentinelBlockHandler(BlockException e) {
    return "當前訪問人數過多,請稍後重試!";
}

啓動訂單服務,此時,可以看到Sentinel控制檯已經出現1中在Nacos配置的流控規則:

 訪問http://localhost:8090/getOrderSentinel,一秒連續兩次請求,則也會走blockHandler方法。

【小結】:注意一定要刪掉 SentinelApplicationRunner類,因爲SpringCloudAlibaba默認Sentinel整合Nacos持久化已經在服務啓動的時候,加載Nacos的配置進服務內存,所以應該註釋掉啓動加載類,否則會產生衝突。

四. Gateway整合Sentinel實現服務限流

上面我們都是在單個服務(訂單服務)演示流控規則,下面我們說一下在微服務網關Gateway中如何整合Sentiel。

官方文檔:https://github.com/alibaba/Sentinel/wiki/網關限流

  1. 首先在網關服務中額外引入以下依賴:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.6.0</version>
</dependency>

  2. 編寫固定配置類(直接拷貝如下代碼即可,無需修改):

@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}

  3. 在網關配置啓動加載類,配置限流規則:

@Slf4j
@Component
public class SentinelApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initGatewayRules();
    }
    // 配置限流規則
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("order")
                // 限流閾值
                .setCount(1)
                // 統計時間窗口,單位是秒,默認是 1 秒
                .setIntervalSec(1)
        );
        GatewayRuleManager.loadRules(rules);
    }
}


注意new GatewayFlowRule("order")  裏面的order即爲配置文件中routes中的-id,而不是path。

啓動網關,端口爲81,訪問返回如下:

可見返回的提示不太直觀,我們可以更改返回錯誤碼:

  ① 新建Handler實現WebExceptionHandler

public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
    public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }
}

  ② GatewayConfiguration註釋默認的SentinelGatewayBlockExceptionHandler,把自定義的注入到Spring容器中:

//    @Bean
//    @Order(Ordered.HIGHEST_PRECEDENCE)
//    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
//        // Register the block exception handler for Spring Cloud Gateway.
//        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
//    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

重啓項目,一秒連續兩次訪問,返回結果如下:(getOrderSentinel有自己的降級方法,網關優先級更高,先被執行)

五. Sentinel熔斷降級

官方文檔:https://github.com/alibaba/Sentinel/wiki/熔斷降級

服務降級的策略:

1. rt(平均響應時間)【注意是單位時間內,並不是累加】

如果在1s內訪問五次,平均的響應時間超出了我們在平臺設置的閾值的情況下,直接觸發我們的熔斷執行我們服降級的方法。

 在規定的時間窗口內(單位爲秒)一直執行我們的服務降級的方法,不能夠執行我們的真實業務邏輯。

    // 平均響應時間RT
    @SentinelResource(value = "getOrderRt", fallback = "getOrderRtFallback")
    @RequestMapping("/getOrderRt")
    public String getOrderRt() {
        try {
            Thread.sleep(300);
        } catch (Exception e) {
        }
        return "正常執行我們業務邏輯";
    }

    public String getOrderRtFallback() {
        return "Rt - 服務降級";
    }

    

一秒連續訪問5次接口,則會走降級方法,因爲平均響應時間肯定大於10,下次訪問則走降級方法,3秒後解除。

    

注意:① 代碼中Thread.sleep(300),如果直接return如上圖IDEA代碼,則平均響應時間肯定不會大於10,則一秒內訪問10次甚至更多,也不會走降級方法

一旦服務重啓後,降級規則失效,需要重新配置,同理也可以用Nacos持久化,自行學習,這裏不討論了~

2. 異常比例

比如客戶端每秒s內發出5個請求,5個請求全部錯誤,這說明錯誤率爲百分百。

每秒內發出5個請求,如果請求的異常佔比超過我們設置的閾值佔比的情況下,就會出發我們熔斷,執行我們的服務降級方法,在規定的時間窗口內(單位爲秒)不能執行我們真實業務邏輯。

    @SentinelResource(value = "getOrderException", fallback = "getOrderExceptionFallback")
    @RequestMapping("/getOrderException")
    public String getOrderException(int age) {
        int j = 1 / age;
        return "正常執行我們業務邏輯:j" + j;
    }

    public String getOrderExceptionFallback(int age) {
        return "錯誤率太高,展示無法訪問該接口";
    }

    

每秒請求 >= 5次,異常比例爲100%,則走降級,同樣持續3秒。

    

注意:如果代碼中手動catch,則出異常直接進catch返回了,不會走降級方法,永遠不會走~

3. 異常數

當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。注意由於統計時間窗口是分鐘級別的(單位爲分鐘),若 timeWindow 小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態。

該方式也用getOrderException測試(同一個資源名稱可以有多個降級方式),會發現近一分鐘連續出現5次異常,第6次訪問則會走降級方法,注意時間窗口單位爲分鐘,所以解封時間爲1分鐘。注意該方式手動catch,則也不會走降級方法。

【總結】:個人認爲,異常比例和異常數沒多大用處,Rt平均響應時間還是比較重要的;順便說一下fallback與blockHandler的區別:fallback是服務熔斷或者業務邏輯出現異常執行的方法(1.6版本以上),blockHandler 限流出現錯誤執行的方法。

六. Sentinel熱點參數限流

官方文檔:https://github.com/alibaba/Sentinel/wiki/熱點參數限流

何爲熱點?熱點即經常訪問的數據。很多時候我們希望統計某個熱點數據中訪問頻次最高的 Top K 數據,並對其訪問進行限制。比如:

  • 商品 ID 爲參數,統計一段時間內最常購買的商品 ID 並進行限制
  • 用戶 ID 爲參數,針對一段時間內頻繁訪問的用戶 ID 進行限制

熱點參數限流會統計傳入參數中的熱點參數,並根據配置的限流閾值與模式,對包含熱點參數的資源調用進行限流。熱點參數限流可以看做是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。

Sentinel 利用 LRU 策略統計最近最常訪問的熱點參數,結合令牌桶算法來進行參數級別的流控。熱點參數限流支持集羣模式。

1. 基本使用:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel</artifactId>
    <version>0.2.2.RELEASE</version>
</dependency>
@RestController
@Slf4j
public class SeckillService {

    // 秒殺的限流規則
    private static final String SEKILL_RULE = "seckill";

    public SeckillService() {
        // Spring容器初始化時把熱點參數配置加載到jvm內存
        initSeckillRule();
    }

    private void initSeckillRule() {
        ParamFlowRule rule = new ParamFlowRule(SEKILL_RULE)
                // 對我們秒殺接口第0個參數實現限流
                .setParamIdx(0)
                .setGrade(RuleConstant.FLOW_GRADE_QPS)
                // 每秒QPS最多訪問1次,如一秒2次即開始限流
                .setCount(1);
        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
        log.info(">>>秒殺接口限流策略配置成功<<<");
    }

    // 秒殺接口
    @RequestMapping("/seckill")
    public String seckill(Long userId, Long orderId) {
        try {
            Entry entry = SphU.entry(SEKILL_RULE, EntryType.IN, 1, userId);
            return "秒殺成功";
        } catch (Exception e) {
            return "當前該用戶訪問頻率過多,請稍後重試!";
        }
    }
}

啓動服務,訪問接口(帶userId),一秒第二次訪問則會走catch:

      

注意:如果不傳熱點參數userId,則永遠都不會走限流,如http://localhost:8090/seckill?order=123123

2. 控制檯自定義:(推薦)

@RestController
@Slf4j
public class SeckillService {

    // 秒殺的限流規則
    private static final String SEKILL_RULE = "seckill";

    @RequestMapping("/seckill")
    @SentinelResource(value = SEKILL_RULE)
    public String seckill(Long userId, Long orderId) {
        return "秒殺成功";
    }

}
@RestControllerAdvice
public class InterfaceExceptionHandler {

    @ResponseBody
    @ExceptionHandler(ParamFlowException.class)
    public String businessInterfaceException(ParamFlowException e) {
        return "您當前訪問的頻率過高,請稍後重試!";
    }
}

重啓服務,控制檯配完熱點規則,效果和上面一樣。

注意:① 窗口時間只能設爲1秒,可能是版本bug (sentinel-dashboard-1.6.2.jar)。

熱點參數限流後,不會走fallback和blockHandler,只能全局捕獲異常來自定義返回信息。

Sentinel支持熱詞參數Vip通道,即可以指定某些參數的規則,這個比如在某些秒殺活動中可以作弊使用,當然僅做了解,還是不推薦這樣做的 ! 方式:編輯熱點規則 - 高級選項,如下圖,指定第一個參數爲136,qps閾值爲10,即一秒最多可以有10次請求,添加後則可以在瀏覽器無限刷新,手速快的除外 ~ 

【本篇總結】

Sentinel可以說是分佈式流量防禦的神器,在SpringCloudAlibba中可以完全替代SpringCloud的Hystrix,易於維護,可持久化,官方文檔詳細,最後再貼出一遍官網文檔:https://github.com/alibaba/Sentinel/wiki/介紹,有不懂的歡迎留言 ~ 

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