Spring Cloud Alibaba(8)使用@SentinelResource註解實現限流 帶源碼

目錄

源碼

參考

背景

自定義資源點 

實現限流控制

實現限流的異常處理 

 實現熔斷降級

熔斷的降級處理 

更多註解屬性說明


源碼

 https://gitee.com/acelee723/acelee-alibaba-sentinelresource

參考

http://blog.didispace.com/spring-cloud-alibaba-sentinel-2-5/

背景

在之前的《Spring Cloud Alibaba(3)Sentinel接口限流整合入門 帶源碼》一文中,我們僅依靠引入Spring Cloud Alibaba對Sentinel的整合封裝spring-cloud-starter-alibaba-sentinel,就完成了對所有Spring MVC接口的限流控制。然而,在實際應用過程中,我們可能需要限流的層面不僅限於接口。可能對於某個方法的調用限流,對於某個外部資源的調用限流等都希望做到控制。那麼,這個時候我們就不得不手工定義需要限流的資源點,並配置相關的限流策略等內容了。

今天這篇我們就來一起學習一下,如何使用@SentinelResource註解靈活的定義控制資源以及如何配置控制策略。

自定義資源點 

1.新建一個spring boot項目acelee-alibaba-sentinelresource,pom.xml文件添加sentinel依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.aceleeyy</groupId>
    <artifactId>acelee-alibaba-sentinelresource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>acelee-alibaba-sentinelresource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>0.9.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.在應用啓動主類中增加註解支持的配置 

package com.aceleeyy.aceleespringcloudalibaba;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class AceleeSpringcloudAlibabaApplication {

    public static void main(String[] args) {
        SpringApplication.run(AceleeSpringcloudAlibabaApplication.class, args);
    }


    // 註解支持的配置Bean
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }

}

3.新建service和serviceImpl的包,對應增加接口類和實現類,添加一個doSomething()的方法

 

1)HelloService

package com.aceleeyy.aceleespringcloudalibaba.service;

/**
 * TODO
 *
 * @author Ace Lee
 * @date 2019/9/23 14:53
 * @version 1.0
 **/
public interface HelloService {

    void doSomething(String s);
}

2)HelloServiceImpl

 在需要通過Sentinel來控制流量的地方使用@SentinelResource註解,比如下面以控制Service邏輯層的某個方法爲例:

package com.aceleeyy.aceleespringcloudalibaba.service.impl;

import com.aceleeyy.aceleespringcloudalibaba.service.HelloService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;

/**
 * TODO
 *
 * @author Ace Lee
 * @date 2019/9/23 14:54
 * @version 1.0
 **/
@Service
public class HelloSericeImpl implements HelloService {


    @SentinelResource(value = "doSomething")
    @Override
    public void doSomething(String s) {
        System.out.println("doSomething------------"+s);
    }
}

到這裏一個需要被保護的方法就定義完成了。下面我們分別說說,定義了資源點之後,我們如何實現不同的保護策略,包括:限流、降級等。 

 

如何實現限流與熔斷降級

在定義了資源點之後,我們就可以通過Dashboard來設置限流和降級策略來對資源點進行保護了。同時,也可以通過@SentinelResource來指定出現限流和降級時候的異常處理策略。下面,就來一起分別看看限流和降級都是如何實現的。

實現限流控制

1.新建一個controller包,下面新建一個HelloController.java的類,加一個hello的http測試接口,裏面調用HelloService的doSomething()方法

package com.aceleeyy.aceleespringcloudalibaba.controller;

import com.aceleeyy.aceleespringcloudalibaba.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello() {
        helloService.doSomething("hello"+new Date());
        return "aceleeyy.com";
    }

}

2.啓動Sentinel-Dashboard,參考《Spring Cloud Alibaba(3)Sentinel接口限流整合入門 帶源碼》

3.增加應用程序配置,啓動應用

application.properties

spring.application.name=acelee-alibaba-sentinelresource
server.port=8001

# sentinel dashboard
spring.cloud.sentinel.transport.dashboard=localhost:8080

4.訪問http://localhost:8001/hello,返回:aceleeyy.com,查看日誌

5.這個時候查看sentinel dashboard,可以看到除了如之前入門實例中那樣有/hello資源點之外,多了一個doSomething資源點。可以通過界面爲這個資源點設置限流規則,比如將其QPS設置爲2。由於/hello資源不設置限流規則,所以只要請求/hello接口,就可以直接模擬調用doSomething資源,來觀察限流規則是否生效。

6.配置doSomething 的流控規則,配置如下圖所示

7.快速調用/hello接口幾次,只要QPS超過2,那麼就會出現如下的錯誤返回,代表限流策略生效了

實現限流的異常處理 

默認情況下,Sentinel對控制資源的限流處理是直接拋出異常,也就是上一節中貼出的日誌內容。在沒有合理的業務承接或者前端對接情況下可以這樣,但是正常情況爲了更好的用戶業務,都會實現一些被限流之後的特殊處理,我們不希望展示一個生硬的報錯。那麼只需要基於上面的例子做一些加工,比如:

HelloServiceImpl

package com.aceleeyy.aceleespringcloudalibaba.service.impl;

import com.aceleeyy.aceleespringcloudalibaba.service.HelloService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * TODO
 *
 * @author Ace Lee
 * @date 2019/9/23 14:54
 * @version 1.0
 **/
@Slf4j
@Service
public class HelloSericeImpl implements HelloService {


    @SentinelResource(value = "doSomething", blockHandler = "exceptionHandler")
    @Override
    public void doSomething(String s) {
        System.out.println("doSomething------------"+s);
    }

    // 限流與阻塞處理
    public void exceptionHandler(String str, BlockException ex) {
        log.error( "blockHandler:" + str, ex);
    }
}

主要做了兩件事:

  • 通過@SentinelResource註解的blockHandler屬性制定具體的處理函數
  • 實現處理函數,該函數的傳參必須與資源點的傳參一樣,並且最後加上BlockException異常參數;同時,返回類型也必須一樣。

如果熟悉Hystrix的讀者應該會發現,這樣的設計與HystrixCommand中定義fallback很相似,還是很容易理解的。

完成上面的改動之後,重新啓動應用,再快速訪問接口(注意限流規則需要配置好),此時前端就不會返回異常信息了,後端會打印exceptionHandler中定義的日誌輸出。而在實際應用的時候,只要根據業務需要對限流請求做緩存或者前端提示等都可以基於此方法來實現。

 實現熔斷降級

@SentinelResource註解除了可以用來做限流控制之外,還能實現與Hystrix類似的熔斷降級策略。下面就來具體看看如何使用吧。

1.還是以上面項目爲例,在HelloService裏添加doSomething2()的方法

HelloService

package com.aceleeyy.aceleespringcloudalibaba.service;

/**
 * TODO
 *
 * @author Ace Lee
 * @date 2019/9/23 14:53
 * @version 1.0
 **/
public interface HelloService {

    void doSomething(String s);

    void doSomething2(String s);
}

HelloServiceImpl

與限流控制一樣,使用@SentinelResource註解標記資源點,比如:

package com.aceleeyy.aceleespringcloudalibaba.service.impl;

import com.aceleeyy.aceleespringcloudalibaba.service.HelloService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * TODO
 *
 * @author Ace Lee
 * @date 2019/9/23 14:54
 * @version 1.0
 **/
@Slf4j
@Service
public class HelloSericeImpl implements HelloService {


    @SentinelResource(value = "doSomething", blockHandler = "exceptionHandler")
    @Override
    public void doSomething(String s) {
        System.out.println("doSomething------------"+s);
    }

    @SentinelResource(value = "doSomething2")
    @Override
    public void doSomething2(String s) {
        log.info("doSomething2------------"+s);
        throw new RuntimeException("發生異常");
    }

    // 限流與阻塞處理
    public void exceptionHandler(String str, BlockException ex) {
        log.error( "blockHandler:" + str, ex);
    }
}

這裏在實現類中創建了一個新的方法,並使用@SentinelResource將該資源命名爲doSomething2。該方法會拋出異常,以配合後續制定基於異常比例的降級策略(類似Hystrix)。Sentinel相比Hystrix更豐富,還有基於響應時間和異常數的降級策略。 

2.在HelloController裏添加接口,調用doSomething2()方法

package com.aceleeyy.aceleespringcloudalibaba.controller;

import com.aceleeyy.aceleespringcloudalibaba.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello() {
        helloService.doSomething("hello"+new Date());
        return "aceleeyy.com";
    }

    @GetMapping("/hello2")
    public String hello2() {
        helloService.doSomething2("hello2"+new Date());
        return "aceleeyy.com";
    }

}

3.啓動應用,調用/hello2接口,可以看到“發生異常”日誌

 4.查看Sentinel-Dashboard,可以看到簇點鏈路名爲doSomeThing2的資源點。然後點擊”降級“按鈕,爲該資源設置降級規則。這裏使用異常比例策略,比例設置爲0.5(即:50%的異常率),時間窗口設置爲2(秒)。

5.驗證熔斷降級,根據上面的降級策略配置,當doSomeThing2方法的調用QPS >= 5,如果異常率超過50%,那麼後續2秒內的調用將直接出發熔斷降級,默認情況會直接拋出DegradeException異常,比如: 

熔斷的降級處理 

在Sentinel中定義熔斷的降級處理方法非常簡單,與Hystrix非常相似。只需要使用@SentinelResource註解的fallback屬性來指定具體的方法名即可。這裏也需要注意傳參與返回必須一致。比如:

 HelloServiceImpl

package com.aceleeyy.aceleespringcloudalibaba.service.impl;

import com.aceleeyy.aceleespringcloudalibaba.service.HelloService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * TODO
 *
 * @author Ace Lee
 * @date 2019/9/23 14:54
 * @version 1.0
 **/
@Slf4j
@Service
public class HelloSericeImpl implements HelloService {


    @SentinelResource(value = "doSomething", blockHandler = "exceptionHandler")
    @Override
    public void doSomething(String s) {
        System.out.println("doSomething------------"+s);
    }

    
    @SentinelResource(value = "doSomething2", fallback = "fallbackHandler")
    @Override
    public void doSomething2(String s) {
        log.info("doSomething2------------"+s);
        throw new RuntimeException("發生異常");
    }

    // 限流與阻塞處理
    public void exceptionHandler(String str, BlockException ex) {
        log.error( "blockHandler:" + str, ex);
    }

    // 熔斷與降級處理
    public void fallbackHandler(String str) {
        log.error("fallbackHandler:" + str);
    }
}

完成上面的改造之後,重啓應用,並設置doSomeThing2資源的熔斷降級策略(使用異常百分比),然後頻繁的請求/hello2接口。在QPS>=5之後,由於這個接口一直在拋出異常,所以一定會滿足熔斷降級條件,這時候就會執行fallbackHandler方法,不斷的打印如下日誌: 

 

更多註解屬性說明

關於@SentinelResource註解最主要的兩個用法:限流控制熔斷降級的具體使用案例介紹完了。另外,該註解還有一些其他更精細化的配置,比如忽略某些異常的配置、默認降級函數等等,具體可見如下說明:

  • value:資源名稱,必需項(不能爲空)
  • entryType:entry 類型,可選項(默認爲 EntryType.OUT
  • blockHandler / blockHandlerClassblockHandler對應處理 BlockException 的函數名稱,可選項。blockHandler 函數訪問範圍需要是 public,返回類型需要與原方法相匹配,參數類型需要和原方法相匹配並且最後加一個額外的參數,類型爲 BlockException。blockHandler 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 blockHandlerClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,否則無法解析。
  • fallback:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore裏面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:
    • 返回值類型必須與原函數返回值類型一致;
    • 方法參數列表需要和原函數一致,或者可以額外多一個 Throwable 類型的參數用於接收對應的異常。
    • fallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,否則無法解析。
  • defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,通常用於通用的 fallback 邏輯(即可以用於很多服務或方法)。默認 fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore裏面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。defaultFallback 函數簽名要求:
    • 返回值類型必須與原函數返回值類型一致;
    • 方法參數列表需要爲空,或者可以額外多一個 Throwable 類型的參數用於接收對應的異常。
    • defaultFallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,否則無法解析。
  • exceptionsToIgnore(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。

注:1.6.0 之前的版本 fallback 函數只針對降級異常(DegradeException)進行處理,不能針對業務異常進行處理

特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandlerfallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出

參考資料Sentinel官方文檔

 


歡迎關注博主博客,後期博主會持續更新spring cloud alibaba 系列文章,敬請期待! 

 

 

 

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