雪崩效應java
雪崩效應如上圖所示,咱們在微服務中的調用鏈中,當一個基礎微服務的API接口A不可用時,當B調用A的服務會堆積阻塞,由於咱們知道咱們每一次調用,不管是調用方仍是服務提供方,其實都是一個線程,而這些線程通常都是線程池中的線程。通常一個線程池中的線程數是有限的,一直到請求超時的時候,這個線程纔會被釋放(在正常狀況下,任務執行完畢,線程釋放,因此要求每一個調用的執行時間越短越好,便於線程池中的線程不斷重複使用,不出現阻塞)。在高併發的狀況下,B的線程池中的線程資源會被瞬間徹底佔用,在短時間內再也沒法建立線程來執行任務,因而B停擺,掛掉了。同理,C、D服務在後續調用中也被B搞掛了。咱們把基礎服務故障,致使上層服務故障,而且這個故障不斷放大的過程,稱爲雪崩效應。linux
常見容錯方案git
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接口的流量。
熱點注意點
系統規則
咱們進入系統規則標籤,點新增系統規則按鈕會看到如圖所示的界面。
這裏有幾個選項
當系統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
受權規則
假如咱們要給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)); } }