一、服務雪崩
問題描述
我們的系統由微服務架構組成,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個,則觸發降級。