Sentinel搭建流程

雪崩效應java

雪崩效應如上圖所示,咱們在微服務中的調用鏈中,當一個基礎微服務的API接口A不可用時,當B調用A的服務會堆積阻塞,由於咱們知道咱們每一次調用,不管是調用方仍是服務提供方,其實都是一個線程,而這些線程通常都是線程池中的線程。通常一個線程池中的線程數是有限的,一直到請求超時的時候,這個線程纔會被釋放(在正常狀況下,任務執行完畢,線程釋放,因此要求每一個調用的執行時間越短越好,便於線程池中的線程不斷重複使用,不出現阻塞)。在高併發的狀況下,B的線程池中的線程資源會被瞬間徹底佔用,在短時間內再也沒法建立線程來執行任務,因而B停擺,掛掉了。同理,C、D服務在後續調用中也被B搞掛了。咱們把基礎服務故障,致使上層服務故障,而且這個故障不斷放大的過程,稱爲雪崩效應。linux

常見容錯方案git

  • 超時 咱們在每次請求中都設置一個較短的超時時間,好比1秒,這樣,只要線程釋放的速度夠快,那麼在上圖中的B服務就不那麼容易被A服務拖死。
  • 限流 咱們知道,一個請求會有一個最大的吞吐量qps,咱們假設C、D調用B服務的請求達到一個閾值,即使A服務掛了,那麼B請求A會在不斷正常超時的狀況下,依然能夠釋放線程,那麼咱們能夠給B作這樣的限流,不讓C、D請求B的併發量過大,使得B能夠正常運行。
  • 倉壁模式 給同一個服務不一樣的Controller設立不一樣的線程池,彼此不影響,而且每個Controller的獨立線程池的線程數並不大。
  • 斷路器模式 給具體的某一個Restful API進行監控,若是在設定的時間內,好比5秒內的錯誤率,錯誤次數達到一個閾值,斷路器自動切斷該API的訪問,斷路器處於打開狀態。在一段時間後,斷路器會處於半開狀態,會進行一種嘗試性的訪問,若是此時訪問成功,斷路器會關閉,容許該接口從新被正常訪問。在次模式下,好比上圖中的A服務失敗,B服務訪問A若干次後,失敗,斷路器關閉B服務的訪問,若A服務恢復使用,則B服務無需人工干預,則本身能夠恢復訪問。

Sentinel的控制檯安裝github

控制檯下載地址 https://github.com/alibaba/Sentinel/releasesweb

根據你本身的版本挑選Sentinel的版本號,我這裏使用的是1.7.0spring

下載好後使用docker

java -jar sentinel-dashboard-1.7.0.jarjson

使用docker安裝控制檯瀏覽器

docker pull bladex/sentinel-dashboard:1.7.0服務器

docker run -d --name sentinel -p 8858:8858 bladex/sentinel-dashboard:1.7.0

這裏須要注意的是用docker啓動的該實例端口號爲8858,而不是咱們正常啓動jar包的8080

啓動後,界面以下,訪問端口8080(以jar包啓動爲例)

輸入用戶名sentinel,密碼sentinel後登陸後,界面以下,目前控制檯中,什麼都沒有。

項目整合控制檯

pom

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

添加配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
  application:
    name: nacos

啓動該項目,訪問該項目的一個依賴於user的接口

@RestController
public class BalanceController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/trace")
    public String trace() {
        return restTemplate.getForObject("http://user/find", String.class);
    }
}

因爲user未啓動,訪問結果以下

刷新Sentinel控制檯(此處的nacos是項目名叫nacos)

咱們多刷新幾回trace接口的訪問,此時再看到實時監控已經有數據了

在簇點鏈路中,咱們能夠看到咱們訪問過的url

咱們點流控按鈕,能夠對該訪問API進行流控設置

好比我此處設置QPS爲1的時候,設置爲訪問直接失敗,點保存,當咱們不斷的點瀏覽器刷新,就會出現訪問被限流點狀況。

第二個關聯,當關聯的資源達到閾值,就限流本身。

比方說咱們有另一個接口/trace

如今我用代碼一直去訪問這個/trace

public class TestFind {
    public static void main(String[] args) throws InterruptedException {
        RestTemplate restTemplate = new RestTemplate();
        for (int i = 0;i < 10000;i++) {
            restTemplate.getForObject("http://127.0.0.1:8081/trace", String.class);
            Thread.sleep(500);
        }
    }
}

此時,這個/trace的QPS確定是大於1的。咱們用瀏覽器來訪問/find,結果以下

此處能夠考慮的業務場景爲某一個資源的讀寫優先級,根據業務需求,來進行關聯限流。

第三個鏈路 只記錄指定鏈路上的流量(經測試好像無效,正常配置以下)

如今來寫一個測試代碼

@Slf4j
@Service
public class CommonService {
    @SentinelResource("common")
    public String common() {
        log.info("Common");
        return "Common";
    }
}
@RestController
public class CommonController {
    @Autowired
    private CommonService commonService;

    @GetMapping("test-a")
    public String testA() {
        return commonService.common();
    }

    @GetMapping("test-b")
    public String testB() {
        return commonService.common();
    }
}

咱們訪問test-a

咱們能夠在簇點鏈路中找到此次訪問的狀況,咱們會發如今/test-a下面有一個common,點擊common的流控

進行以下設置

因爲該設置無效,就不提供測試結果了。可是對資源點common設置"直接"是有效的。

Warm Up(預熱) 根據codeFactor(冷加載因子,默認3)的值,從閾值/codeFactor,通過預熱時長,才達到設置的QPS閾值。

這裏設置的意思是一開始的QPS閾值爲100/3,在10秒內不斷上升到100。這裏主要使用於一開始有大量突增激發流量到狀況下,設置爲該模式,不讓這種突發到併發訪問撐破服務器。

排隊等待 勻速排隊,讓請求以均勻到速度經過,閾值類型必須設置爲QPS,不然無效。

這個圖的意思是1秒內只有1個請求容許經過,若是這個請求達到超時時間,就丟棄該請求。

用代碼來測試

@Slf4j
public class OneByOneTest {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        for (int i = 0;i < 10000;i++) {
            long start = System.currentTimeMillis();
            String object = restTemplate.getForObject("http://127.0.0.1:8081/test-a", String.class);
            log.info(object + ",間隔時間:" + (System.currentTimeMillis() - start));
        }
    }
}

運行結果(部分):

15:44:15.463 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:15.474 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:15.482 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:15.483 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:15.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:45
15:44:15.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:15.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:16.484 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:16.484 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:16.484 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:998
15:44:16.485 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:16.485 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:17.483 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:17.483 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:17.483 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:999
15:44:17.484 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:17.484 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:18.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:18.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:18.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:1002
15:44:18.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:18.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:19.481 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:19.481 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:19.481 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:996
15:44:19.482 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:19.482 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:20.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:20.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:20.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:1004
15:44:20.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:20.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:21.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:21.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:21.486 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:1001
15:44:21.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:21.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:22.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:22.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:22.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,間隔時間:999

咱們能夠看到,基本上每次請求的間隔爲1秒。沒有一個請求被丟棄。而以前的兩種——快速失敗和Worm Up,都會丟棄請求。該方式適用於流量訪問不均衡的狀況,有激增時段,有空閒時段,當激增時段到來時,不斷讓流量緩緩經過,用空閒時段來慢慢處理。

降級規則設置

咱們繼續使用/test-a來講明

點擊降級按鈕

這裏RT爲平均響應時間,上面設置爲1毫秒,時間窗口5,這裏的整體意思表示以下

平均響應時間(秒級統計)超出閾值(此處爲大於1毫秒)而且在5秒內經過的請求>=5次——>觸發降級(熔斷器打開)——>時間窗口5秒結束——>關閉降級

咱們訪問一次該接口,能夠看到單次的相應時間爲3毫秒,這個3毫秒是確定大於1毫秒的。咱們不斷的刷新該接口,當咱們連續刷5次,到第6次的時候

觸發降級策略,

那麼在5秒內,咱們的訪問就會一直是限流狀態,直到5秒後才能夠正常訪問。

降級注意點

RT默認最大4900ms

這裏就是說這個RT設置的再大,好比100000,它依然仍是按4900ms來執行,若是必定要修改這個值,能夠經過-Dcsp.sentinel.statistic.max.rt=xxx修改

降級-異常比例

QPS >= 5而且異常比例(秒級統計,0.0-1.0)超過閾值——>觸發降級(熔斷器打開)——>時間窗口5秒結束——>關閉降級

降級-異常數

異常數(分鐘統計)超過閾值——>觸發降級(熔斷器打開)——>時間窗口65秒結束——>關閉降級。

此處不一樣於RT和異常比例都是以秒級統計,而異常數是以分鐘統計的。

注意點 時間窗口<60秒可能會出問題。好比作以下配置

這裏的意思爲當1分鐘之內的異常數大於10,就會觸發降級,直到10秒的時間窗口結束,關閉降級。但異常數的統計是在分鐘級別的,可能10秒結束的時候依然在1分鐘之內,異常數依然大於10次,那麼就會再次進入降級。因此時間窗口建議設置大於等於60的值。

由此能夠看出,相對於斷路器三態狀態,Sentinel的斷路器沒有半開狀態。但可能會在將來增長。

熱點規則

添加一個Controller方法以下

@GetMapping("/testhost")
@SentinelResource("hot")
public String testHost(@RequestParam(value = "a",required = false) String a,@RequestParam(value = "b",required = false) String b) {
    return a + " " + b;
}

訪問該方法

在Sentinel控制檯的蔟點鏈路中,點擊hot的熱點按鈕

這裏的參數索引0指的是@RequestParam(value = "a",required = false) String a,參數索引從0開始,若是設爲1則爲@RequestParam(value = "b",required = false) String b。這裏表示若是有參數a傳輸過來,則在1秒內只容許有1個請求能夠訪問。若是咱們不填參數a.

則不管咱們怎麼刷新,都不會被限流。

咱們再打開高級選項,參數類型選擇a的類型String,參數值abc(此處好像不支持中文),限流閾值1000.添加,保存後。

若是咱們將a值填爲abc,此時不管咱們如何刷新都不會被限流

應用場景:若是咱們發現某一個熱詞的提交很是高,則咱們能夠設置熱點規則,進行對單個熱詞進行限流,而不限制整個API接口的流量。

熱點注意點

  • 參數必須是基本類型或者String

系統規則

咱們進入系統規則標籤,點新增系統規則按鈕會看到如圖所示的界面。

這裏有幾個選項

  • Load 負載

當系統load1(1分鐘的load)超過閾值,且併發線程數超過系統容量時觸發,建議設置爲CPU核數*2.5。(僅對Linux/Unix-like機器生效)

這裏須要知道什麼是load1,能夠由linux uptime命令來看

[root@localhost ~]# uptime
 10:46:36 up 11 days, 19:57,  1 user,  load average: 0.10, 0.11, 0.13

這裏的0.10被稱爲load1,意思就是1分鐘之內的系統平均負載;0.11被稱爲load5,5分鐘之內的系統平均負載,0.13被稱爲load15,15分鐘內的系統平均負載。

系統容量 = maxQps * minRt

  1. maxQps: 秒級統計出來的最大QPS
  2. minRt: 秒級統計出來的最小響應時間
  • RT 平均響應時間,全部入口流量的平均RT達到閾值觸發。
  • 線程數 全部入口流量的併發線程數達到閾值觸發。
  • 入口QPS 全部入口流量的QPS達到閾值觸發。

受權規則

假如咱們要給find添加受權規則

這裏指的是受權給哪個微服務添加黑白名單,白名單指的是隻容許某一個微服務訪問,黑名單是禁止某一個微服務訪問。

代碼配置規則

其實以上的控制檯的設置均可以直接在項目中使用代碼來配置

  • 流控規則

Field 說明 默認值
resource 資源名,資源名是限流規則的做用對象  
count 限流閾值  
grade 限流閾值類型,QPS或線程數模式 QPS模式
limitApp 流控針對的調用來源 default,表明不區分調用來源
strategy 判斷的根據是資源自身,仍是根據其餘關聯資源(refResource),仍是根據鏈路入口 根據資源自己
controlBehavior 流控效果(直接拒絕/排隊等待/慢啓動模式) 直接拒絕

配置代碼以下

@Component
public class FlowRuleConfig {
    @PostConstruct
    public void init() {
        initFlowQpsRule();
    }

    private void initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule("/find");
        rule.setCount(1);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

這段代碼跟以前的控制檯的配置效果是同樣的。

  • 降級規則

Field 說明 默認值
resource 資源名,即限流規則的做用對象  
count 閾值  
grade 降級模式,根據RT降級仍是根據異常比例降級 RT
timeWindow 降級的時間,單位爲s  

配置代碼以下

@Component
public class FlowRuleConfig {
    @PostConstruct
    public void init() {
        initDegradeRule();
    }

    private void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource("/find");
        rule.setCount(1);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        rule.setTimeWindow(5);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }
}

當咱們在5秒內訪問/find到6次以後開始降級,並在5秒內不容許訪問該接口,直到5秒後才能夠正常訪問。

  • 熱點規則

屬性 說明 默認值
resource 資源名,必填  
count 限流閾值,必填  
grade 限流模式 QPS模式
durationInSec 統計窗口時間長度(單位爲秒),1.6.0版本開始支持 1s
controlBehavior 流控效果(支持快速失敗和勻速排隊模式),1.6.0版本開始支持 快速失敗
maxQueueingTimeMs 最大排隊等待時長(僅在勻速排隊模式生效),1.6.0版本開始支持 0ms
paramIdx 熱點參數的索引,必填,對應SphU.entry(xxx,args)中的參數索引位置  
paramFlowItemList 參數例外項,能夠針對指定的參數值單獨設置限流閾值,不受前面count閾值的限制。僅支持基本類型  
clusterMode 是不是集羣參數流控規則 false
clusterConfig 集羣流控相關配置  

代碼配置以下

@Component
public class FlowRuleConfig {
    @PostConstruct
    public void init() {
        initParamFlowRule();
    }

    private void initParamFlowRule() {
        ParamFlowRule rule = new ParamFlowRule("hot")
                .setParamIdx(0)
                .setCount(1);
        ParamFlowItem item = new ParamFlowItem().setObject("abc")
                .setClassType(String.class.getName())
                .setCount(1000);
        rule.setParamFlowItemList(Collections.singletonList(item));
        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
    }
}

當咱們在1秒內訪問/testhost接口超過1次的時候就會被限流報錯

可是若是咱們a的值爲abc的時候,則不斷刷新都不受影響

  • 系統規則

Field 說明 默認值
highestSystemLoad 最大的load1 -1(不生效)
avgRt 全部入口流量的平均響應時間 -1(不生效)
maxThread 入口流量的最大併發數 -1(不生效)
qps 全部入口資源的QPS -1(不生效)

代碼配置以下

@Component
public class FlowRuleConfig {
    @PostConstruct
    public void init() {
        initSystemRule();
    }

    private void initSystemRule() {
        List<SystemRule> rules = new ArrayList<>();
        SystemRule rule = new SystemRule();
        rule.setHighestSystemLoad(20);
        rules.add(rule);
        SystemRuleManager.loadRules(rules);
    }
}
  • 受權規則

Field 說明 默認值
resource 資源名,即限流規則的做用對象  
limitApp 對應的黑名單/白名單,不一樣origin用','分割,如appA,appB default,表明不區分調用來源
strategy 限制模式,AUTHORITY_WHITE爲白名單模式,AUTHORITY_BLACK爲黑名單模式,默認爲白名單模式 AUTHORITY_WHITE

代碼配置以下

@Component
public class FlowRuleConfig {
    @PostConstruct
    public void init() {
        initAuthorityRule();
    }

    private void initAuthorityRule() {
        AuthorityRule rule = new AuthorityRule();
        rule.setResource("nacos");
        rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
        rule.setLimitApp("appA,appB");
        AuthorityRuleManager.loadRules(Collections.singletonList(rule));
}
}
相關文章
相關標籤/搜索