SpringCloud-Alibaba-Sentinel

逗你妹發表於2020-06-27

1、概述

Sentinel,中文翻譯為哨兵,是為微服務提供流量控制、熔斷降級的功能,它和Hystrix提供的功能一樣,可以有
效的解決微服務呼叫產生的“雪崩”效應,為微服務系統提供了穩定性的解決方案。隨著Hytrxi進入了維護期,不
再提供新功能,Sentinel是一個不錯的替代方案。通常情況,Hystrix採用執行緒池對服務的呼叫進行隔離,
Sentinel才用了使用者執行緒對介面進行隔離,二者相比,Hystrxi是服務級別的隔離,Sentinel提供了介面級別的
隔離,Sentinel隔離級別更加精細,另外Sentinel直接使用使用者執行緒進行限制,相比Hystrix的執行緒池隔離,減
少了執行緒切換的開銷。另外Sentinel的DashBoard提供了線上更改限流規則的配置,也更加的優化。

從官方文件的介紹,Sentinel 具有以下特徵:

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


 

2、基本使用

1、新建專案,新增pom依賴
    <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

2、編寫配置檔案
server:
  port: 9003
spring:
  application:
    name: cloud-sentinel-9003
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
    nacos:
      discovery:
        server-addr: xxx.xxx.xxx.xxx:8848
management:
  endpoints:
    web:
      exposure:
        include: "*"
***在這裡,sentinel執行在本地,嘗試使用docker,但是獲取不到服務的詳細鏈路,原因是sentinel主動向服務拉取資訊,阿里雲伺服器無法訪問到本機,因此失敗。

3、啟動專案後,訪問兩個埠/testA和/testB,重新整理sentinel即可看到

3、流控規則

3.1、QPS直接快速失敗

每秒如果訪問超過五次,就會丟擲異常

3.2執行緒數直接失敗

當呼叫介面的執行緒數超過閥值時,進行限流,

3.3關聯

當每秒訪問HelloA超過一次後,將對HelloB進行限流,比如支付服務滿了以後對訂單服務進行限流,防止連帶。

3.4鏈路

鏈路流控模式指的是,當從某個介面過來的資源達到限流條件時,開啟限流;它的功能有點類似於針對 來源配置項,區別在於:針對來源是針對上級微服務,而鏈路流控是針對上級介面,也就是說它的粒度 更細;

建立一個service,新增註解

@SentinelResource(value = "getOrder",blockHandler = "handlerException")

在流量監控處設定:

訪問HelloB一秒超過一次就會限流。

注意:sentinel1.7以後需要設定spring.cloud.sentinel.web-context-unify=false即可

詳情:https://github.com/alibaba/Sentinel/issues/1213

3.5預熱

在預熱的5秒內每秒的QPS最多是10/3=3次,如果超過3次就會限流,5秒以後閥值恢復到10

3.6排隊等待

HelloA一秒處理一次請求,超過就排隊等待,等待的超時時間為2000毫秒,超過等待毫秒就會限流。

4、服務降級

1、RT(平均響應時間,秒級)
    平均響應時間超出閥值且在時間視窗內通過的請求次數>=5次,兩個條件同時滿足後觸發降級
    視窗期過後關閉斷路器,RT最大4900

2、異常比例(秒級)
    QPS>=5且異常比例(秒級統計)超過閥值時,觸發降級,時間視窗期結束後,關閉降級

3、異常數(分鐘級)
    異常數(分鐘統計)超過閥值時,觸發降級;時間視窗結束後,關閉降級

4.1RT

一秒持續進入>=5次請求,平均響應時間如果大於200毫秒,則開啟斷路器,在時間視窗期2秒後關閉降級

4.2異常比例

一秒持續進入>=5次請求,如果每秒處理的請求異常比例大於百分之20,則開啟斷路器,在時間視窗期2秒後關閉降級

4.3異常數

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

異常數是按分鐘來統計的,所以時間視窗必須大於等於60s

在61秒內如果超過異常5次,則開啟斷路器,在61秒以後關閉斷路器

5、熱點Key

@GetMapping("/HelloE")
@SentinelResource(value = "helloE",blockHandler = "hostKeyHandler")
public String getE(@RequestParam(value = "a",required = false)String a
                  ,@RequestParam(value = "b",required = false)String b)
{
    return "------HelloE------";
}
public String hostKeyHandler(String a, String b, BlockException ex){
    return ex.getRule().getResource()+",服務正忙,稍後再試~";
}

對HelloE請求,攜帶的第一個引數,如果QPS超過1就會進行降級,返回自定義資訊

新增引數例外項,設定如果第一個引數是2,則閥值是10,第一個引數不是2,則閥值是1

6、系統規則

Sentinel 系統自適應限流從整體維度對應用入口流量進行控制,結合應用的 Load、CPU 使用率、總體平均 
RT、入口 QPS 和併發執行緒數等幾個維度的監控指標,通過自適應的流控策略,讓系統的入口流量和系統的負載達
到一個平衡,讓系統儘可能跑在最大吞吐量的同時保證系統整體的穩定性
Field說明預設值
highestSystemLoadload1 觸發值,用於觸發自適應控制階段-1 (不生效)
avgRt所有入口流量的平均響應時間-1 (不生效)
maxThread入口流量的最大併發數-1 (不生效)
qps所有入口資源的 QPS-1 (不生效)
highestCpuUsage當前系統的 CPU 使用率(0.0-1.0)-1 (不生效)

當任何請求訪問系統QPS超過1,都會開啟斷路器

7、SentinelResource

7.1根據資源名進行限流

@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource()
{
    return new CommonResult(200,"按資源名稱限流測試OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception)
{
    return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服務不可用");
}

根據SentinelResource註解的Value進行限流

7.2根據URL進行限流

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
    return new CommonResult(200,"按url限流測試OK",new Payment(2020L,"serial002"));
}

根據Url進行限流,但使用的是Sentinel自己的提示資訊,不友好

7.3自定義限流處理邏輯

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class,
        blockHandler = "handlerException2")
public CommonResult customerBlockHandler()
{
    return new CommonResult(200,"按客戶自定義",new Payment(2020L,"serial003"));
}
public class CustomerBlockHandler {
  public static CommonResult handlerException2(BlockException exception)
  {
     return new CommonResult(4444,"按客戶自定義,global handlerException----1");
  }

將處理降級提示資訊與業務邏輯分離,使用blockHandlerClass和blockHandler指明類名和方法名即可

SentinelResouce無法對private方法進行監控

8、服務熔斷

1、新建兩個服務提供者和一個消費者,將其註冊到nacos

2、分別通過Ribbion和OpenFeign呼叫服務

pom依賴
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 後續做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

8.1@SentinelResource只配置value

對value進行流控後,會報500錯誤

8.2@SentinelResource只配置fallback

只對系統異常進行降級處理,不會對sentinel設定的流控和降級進行兜底

8.3@SentinelResource只配置blockhandler

只對sentinel設定的流控和降級進行兜底處理,不會對系統異常進行降級處理

8.3@SentinelResource配置fallback和blockhandler

會對異常和sentinel設定的流控和降級進行不同的處理

8.4@SentinelResource中配置exceptionsToIgnore

會忽略設定的異常,不會進行兜底處理,可以自己寫全域性異常處理類進行處理

8.5OpenFeign通過在介面上新增註解,將降級邏輯和業務邏輯分離

新建類實現介面,在過載的方法裡進行降級處理,在介面的註解加上fallback = xxx.class即可

注意需要將實現類加上@Component否則會報錯

@RestController
@Slf4j
public class OrderController {

    @Resource
    private OrderService orderService;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${service.url.paymentUrl}")
    private String paymentUrl;

    // ----------OpenFeign-----------
    @GetMapping("/consumer/getByFeign/{id}")
    public CommonResult getPayByFeign(@PathVariable("id") Long id){
        log.info("**************OpenFeign");
        CommonResult<Payment> result = orderService.paymentSQL(id);
        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法引數異常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,該ID沒有對應記錄,空指標異常");
        }
        return result;
    }
    // ----------Ribbion-----------
    @GetMapping("/consumer/getByRibbion/{id}")
    //@SentinelResource(value = "fallback")
    //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只負責業務異常
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只負責sentinel控制檯配置違規
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
            exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult getByRibbion(@PathVariable("id") Long id){
        log.info("**************Ribbion");
        CommonResult result = restTemplate.getForObject(paymentUrl + "/payment/" + id, CommonResult.class);
        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法引數異常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,該ID沒有對應記錄,空指標異常");
        }
        return result;
    }
    //本例是fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底異常handlerFallback,exception內容  "+e.getMessage(),payment);
    }
    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable  Long id, BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,無此流水: blockException  "+blockException.getMessage(),payment);
    }

}


------------------------------------------------------------
@Component
@FeignClient(value = "cloud-sentinel-payment-provider",fallback = OrderSerivceHandler.class)
public interface OrderService {

    @GetMapping(value = "/payment/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);

}

-----------------------------------------------------------------
@Component
public class OrderSerivceHandler implements  OrderService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444,"服務降級返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

9、持久化規則

將限流配置持久化儲存到Nacos裡

1、新增pom依賴
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>

2、在專案配置檔案裡新增配置如下
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
      datasource:
        wx1: #名字隨意起
          nacos:
            server-addr: xxx.xxx.xxx.xxx:8848 #nacos地址
            dataId: cloud-alibaba-sentinel-service #配置檔案的dataId
            groupId: DEFAULT_GROUP #配置檔案的分組
            data-type: json #配置檔案的型別
            rule-type: flow #用來定義儲存的規則型別,flow代表限流規則
                            #degrade代表降級
                            #system代表系統規則
                            #authority代表授權
3、在Sentinel新增配置檔案如下圖

4、規則說明
resource:資源名稱
limitApp:來源應用
grade:閥值型別,0代表執行緒數,1代表QPS
count:單機閥值
strategy:流控模式,0代表直接,1代表關聯,2代表鏈路
controlBehavior:流控效果,0代表快速失敗,1代表Warm Up(預熱),2代表排隊等待
clusterMode:是否叢集,false代表沒有,true代表叢集

6、監控資料說明
流控的資料只會儲存5分鐘,如果想持久化,可以後期使用:
    1.實現 MetricsRepository 介面;
    2.註冊成 Spring Bean 並在相應位置通過 @Qualifier 註解指定對應的 bean name 即可。


 

10、其他說明

1、sentinel資料持久化有五種方式
    1.File 2.Redis 3.Nacos 4.Zookeeper 5.apollo

2、sentinel預設監控資料只儲存五分鐘,如果想要對監控的資料情況持久化有以下兩種方式
    1.自行擴充套件實現 MetricsRepository 介面;
    2.註冊成 Spring Bean 並在相應位置通過 @Qualifier 註解指定對應的 bean name 即可。