Sentinel 的微服務限流容錯
一、服務雪崩
問題描述
我們的系統由微服務架構組成,A呼叫B,B呼叫C,C呼叫D;在正常情況下,A、B、C、D都是正常的;
當某個時間點服務D突然掛掉了,此時的服務C還在瘋狂的呼叫服務D,由於D已經掛掉了,所以服務C呼叫服務D必須等待服務超時。而每次的C去呼叫服務D的時候都會建立執行緒,高併發的場景C就會阻塞大量的執行緒,那麼服務C就會建立大量的執行緒,當到達一定的程度,服務C也就宕機了。(由於服務D掛掉,導致服務C也跟著宕機了)
就這樣,服務B和服務A也跟著會掛掉,造成服務雪崩。
解決方法
方法一:請求超時設定
配置一下請求超時時間,例如:每次請求在1秒內必須返回,否則到點就把執行緒結束,釋放資源。釋放資源的速度過快,也不會導致服務被拖死。
(1)設定RestTemplate的超時時間
@Configuration public class WebConfig { @Bean public RestTemplate restTemplate() { //設定restTemplate的超時時間 SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setReadTimeout(1000); requestFactory.setConnectTimeout(1000); RestTemplate restTemplate = new RestTemplate(requestFactory); return restTemplate; } }
(2)進行超時異常處理
try{ ResponseEntity<ProductInfo> responseEntity= restTemplate.getForEntity(uri+orderInfo.getProductNo(), ProductInfo.class); productInfo = responseEntity.getBody(); } catch (Exception e) { throw new RuntimeException("呼叫超時"); }
(3)設定全域性異常處理
@ControllerAdvice public class TulingExceptionHandler { @ExceptionHandler(value = {RuntimeException.class}) @ResponseBody public Object dealBizException() { OrderVo orderVo = new OrderVo(); orderVo.setOrderNo("‐1"); orderVo.setUserName("容錯使用者"); return orderVo; } }
方法二:執行緒池隔離模式
M類使用執行緒池1,N類使用執行緒池2,彼此的執行緒池不同,並且為每個類分配的執行緒池大小。
M類呼叫B服務,N類呼叫C服務,如果M類和N類使用相同的執行緒池,那麼如果B服務掛了,M類呼叫B服務的介面併發又很高,你又沒有任何保護措施,你的服務就很可能被M類拖死。而如果M類有自己的執行緒池,N類也有自己的執行緒池,如果B服務掛了,M類頂多是將自己的執行緒池佔滿,不會影響N類的執行緒池,於是N類依然能正常工作。
方法三:斷路器模式
如果發現在一定時間內失敗次數或失敗率達到一定閾值,就“跳閘”,斷路器開啟——此時,請求直接返回,而不去呼叫原本呼叫的邏輯。
在跳閘一段時間後(例如15秒),斷路器會進入半開狀態,這是一個瞬間態,此時允許一次請求再去呼叫,如果成功,則斷路器關閉,應用正常呼叫;如果呼叫依然不成功,斷路器繼續回到開啟狀態,過段時間再進入半開狀態嘗試——通過”跳閘“,應用可以保護自己,而且避免浪費資源;而通過半開的設計,可實現應用的“自我修復“。
二、Sentinel 介紹及使用
官網文件:https://github.com/alibaba/Sentinel/wiki/介紹
1、Sentinel 是什麼?
隨著微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
Sentinel 分為兩個部分:
- 核心庫(Java 客戶端)不依賴任何框架/庫,能夠運行於所有 Java 執行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支援。
- 控制檯(Dashboard)基於 Spring Boot 開發,打包後可以直接執行,不需要額外的 Tomcat 等應用容器。
2、Sentinel的使用
1、引入maven座標
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.7.1</version> </dependency>
2、配置 AspectJ切面SentinelResourceAspect
@Configuration public class SentinelConfig { @Bean public SentinelResourceAspect sentinelResourceAspect(){ return new SentinelResourceAspect(); } }
3、Controller增加限流規則,需限流的方法上添加註解@SentinelResource ,並配置上限流處理的 blockHandler,blockHandlerClass;
@RestController @Slf4j public class HelloController { @PostConstruct public void init() { List<FlowRule> flowRules = new ArrayList<>(); // 建立流控規則 FlowRule flowRule = new FlowRule(); //設定流控規則 QPS flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); //設定受保護的資源 flowRule.setResource("helloSentinelResource"); //設定受保護的資源的閾值 flowRule.setCount(1); flowRules.add(flowRule); //載入配置好的規則 FlowRuleManager.loadRules(flowRules); } @GetMapping("/hello/sentinel") @SentinelResource(value = "helloSentinelResource", blockHandler = "testHelloSentinelBlock", blockHandlerClass = BlockUtil.class) public String sentinelDemo() { log.info("-------- success-hello-sentinel"); return "success-hello-sentinel"; } }
注意:
(1)blockHandler 對應處理 BlockException 的函式名稱( testHelloSentinelBlock );
(2)blockHandler 函式訪問範圍需要是 public,型別必須為 static,返回值型別需要與原方法相匹配;
(3)blockHandler 函式得引數與原方法的引數一樣,並且最後可以加一個型別為 BlockException 的引數;
4、流控處理類
@Slf4j public class BlockUtil { public static String testHelloSentinelBlock(BlockException e) { log.info("block-sentinel---流控了"); return "block-sentinel---流控了"; } }
5、呼叫http://localhost:8080/hello/sentinel 介面的結果(每秒只能通過一個介面):
3、Springboot 整合 Sentinel
(1)maven的座標
<!--加入sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--加入actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
(2)專案的配置檔案增加如下:
spring: cloud: sentinel: transport: dashboard: localhost:9999 application: name: order-center server: port: 8000 # 暴露/actuator/sentinel端點 management: endpoints: web: exposure: include: '*'
注意:必須暴露/actuator/sentinel端點;(http://localhost:8000/actuator/sentinel)
(3) 下載並啟動 sentinel
<1> 下載路徑:https://github.com/alibaba/Sentinel/releases
<2> 啟動 Sentinel :java -jar sentinel-dashboard-1.6.3.jar --server.port=9999
訪問:http://localhost:9999/ ;預設賬號密碼 sentinel/sentinel
(4)進入到 sentinel 的控制檯之後沒有看到裡面的內容,需要啟動一下微服務;
服務啟動發個完成之後去呼叫一下服務的介面:http://localhost:8000/v1/getProduct;
(5)點選 ”+流控“,設定 QPS 的單機閾值為1(即1秒只能通過一個請求)
(6)可以在 ”流控規則“ 中看到我們剛才增加的流控規則。
(7)接著去快速訪問:http://localhost:8000/v1/getProduct,可以在瀏覽器中看到介面被流控了;
三、Sentinel的面板介紹
1、實時監控
可以監控我們介面的 通過QPS 和 拒絕QPS。(注意:沒有設定流控規則的介面是看不到的)
2、簇點鏈路
用來顯示微服務的所監控的API
2.1流控的設定
選擇 ”簇點鏈路“,選擇具體的訪問的API,然後點選 ”流控“ 按鈕。
說明:
資源名稱:介面的URI;
針對來源:這裡是預設的default(標示不針對來源),還有一種情況就是假設微服務A需要呼叫這個資源,微服務B也需要呼叫這個資源,那麼我們就可以單獨的為微服務A和微服務B進行設定閾值。這個功能需求寫程式碼來實現。
閾值型別:分為QPS 和 執行緒數。
若設定閾值為2,
- 選擇QPS,則只要是每秒鐘訪問介面的次數>2就進行限流;
- 選擇執行緒數,為接受請求該資源 分配的執行緒數>2就進行限流.;
流控模式:
- 直接:達到設定的閾值後直接被流控丟擲異常。;
- 關聯:當訪問的介面1,超過設定的閾值,就去限流介面2;
- 鏈路:API級別的限制流量;
流控效果:
- 快速失敗:直接丟擲異常;
- Warm Up(預熱):
當流量突然增大的時候,我們常常會希望系統從空閒狀態到繁忙狀態的切換的時間長一些。即如果系統在此之前長期處於空閒的狀態,我們希望處理請求的數量是緩步的增多,經過預期的時間以後,到達系統處理請求個數的最大值。Warm Up(冷啟動,預熱)模式就是為了實現這個目的的。
冷載入因子: coldFactor 預設是3,,即請求 QPS 從 threshold / 3 開始,經預熱時長逐漸升至設定的 QPS 閾值。
上面設定: 就是QPS從100/3=33開始算 經過10秒鐘,到達 100 的QPS 才進行限制流量。
- 排隊等待:
這種方式適合用於請求以突刺狀來到,這個時候我們不希望一下子把所有的請求都通過,這樣可能會把系統壓垮;同時我們也期待系統以穩定的速度,逐步處理這些請求,以起到“削峰填谷”的效果,而不是拒絕所有請求。
選擇排隊等待的閾值型別必須是QPS。
單機閾值:10表示 每秒通過的請求個數是10,則每隔100ms通過一次請求;每次請求的最大等待時間為2000ms=2s,超過2S就丟棄請求。
2.2 降級規則
除了流量控制以外,對呼叫鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。一個服務常常會呼叫別的模組,可能是另外的一個遠端服務、資料庫,或者第三方 API 等。
降級策略:
- RT:平均響應時間(DEGRADE_GRADE_RT);每秒內的請求的響應時間大於100ms,則統計為慢呼叫;當單位統計時長(
statIntervalMs
)內請求數目大於設定的最小請求數目,並且慢呼叫的比例大於閾值,則接下來的時間視窗(5s)內請求會自動被熔斷。經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求響應時間小於設定的慢呼叫 RT 則結束熔斷,若大於設定的慢呼叫 RT 則會再次被熔斷。
注意:Sentinel 預設統計的 RT 上限是 4900 ms,超出此閾值的都會算作4900 ms,若需要變更此上限可以通過啟動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置 。
介面請求的響應超過100ms,則在接下來的時間視窗5s中,是被熔斷了。
- 異常比例:當單位統計時長(
statIntervalMs
)內請求數目大於設定的最小請求數目,並且異常的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長後熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。異常比率的閾值範圍是[0.0, 1.0]
,代表 0% - 100%。
請求的異常比例大於30%,則在接下來的時間視窗5秒鐘被熔斷。
- 異常數:當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。注意由於統計時間視窗是分鐘級別的,若 timeWindow 小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態,所以這裡的時間視窗設定時間要大於60s。
2.3 熱點引數
熱點引數限流會統計傳入引數中的熱點引數,並根據配置的限流閾值與模式,對包含熱點引數的資源呼叫進行限流。熱點引數限流可以看做是一種特殊的流量控制,僅對包含熱點引數的資源呼叫生效。
(1)使用 @SentinelResource 註解,程式碼如下:
@SentinelResource("order-1-res")
@GetMapping("/v1/orderSave")
public String saveOrder(@RequestParam(value = "userId",required = false)String userId,
@RequestParam(value = "productId" ,required = false) String productId,
@RequestParam(value = "name", required = false) String name) {
log.info("----------- start res ---------");
return userId;
}
(2)配置熱點引數:
資源 order-1-res 的介面的有攜帶有第2個引數,每秒通過的請求數超過3個,則觸發降級,10s 後恢復正常。
http://localhost:8000/v1/orderSave?userId=1 --------------- 不會降級,因為只攜帶了1個引數。
http://localhost:8000/v1/orderSave?userId=1&name=zhangsan&productId=2--------------- 每秒的請求超過3個就會降級
資源 order-1-res 的介面的 第2個引數的值為 wang,則每秒通過的請求超過1個,則觸發降級,10s後恢復正常。第2個引數的值為 wang 之外的請求,則每秒超過的請求超過3個,則觸發降級。