阿里二面:外部接口大量超时,把整个系统拖垮,引发雪崩!如何解决?熔断......
互聯(lián)網(wǎng)+ 時代,業(yè)務數(shù)字化已經(jīng)蔓延到你能想到的各個行業(yè)。各種業(yè)務功能、營銷玩法越來越多,系統(tǒng)也越來越復雜。
面對不斷復雜的業(yè)務系統(tǒng),腦子越來越不夠用了
于是 聰明的人們 提出了 微服務 的設計思想
本著 復雜的事情簡單化 的原則,我們將一個大的系統(tǒng)拆分成若干個子系統(tǒng),每個 子系統(tǒng) 職責單一,按 DDD 的設計理念,承載一個子域的業(yè)務建設。
于是,人們可以將精力聚焦,專心完成某一個業(yè)務點的深度建設。
多個微服務系統(tǒng)之間通過 RPC 框架(如:dubbo、spring cloud、gRPC 等)完成了串聯(lián),但隨著調(diào)用量越來越大,人們發(fā)現(xiàn)服務與服務之間的穩(wěn)定性變得越來越重要
舉個例子:
Service D 掛了,響應很慢
Service G 和 Service F ,都依賴 Service D,也會受到牽連,對外響應也會變慢
影響層層向上傳遞,Service A 和 Service B 也會被拖垮
最后,引發(fā)雪崩效應,系統(tǒng)的故障影響面會越來越大
為了解決這種問題,我們需要引入 熔斷 機制。“當斷則斷,不受其亂。當斷不斷,必受其難”
什么是熔斷?
熔斷,其實是對調(diào)用鏈路中某個資源出現(xiàn)不穩(wěn)定狀態(tài)時(如:調(diào)用超時或異常比例升高),對這個資源的調(diào)用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯(lián)錯誤。
當資源被降級后,在接下來的降級時間窗口內(nèi),對該資源的調(diào)用都自動熔斷(默認是拋出 BlockException)
目前市面上的熔斷框架很多,如:Sentinel、Hystrix、Resilience4j 等,這些框架的設計理念都差不多。
本文重點講下 Sentinel 是如何在項目中使用的
Sentinel (分布式系統(tǒng)的流量防衛(wèi)兵) 是阿里開源的一套用于服務容錯的綜合性解決方案。它以流量為切入點, 從流量控制、熔斷降級、系統(tǒng)負載保護等多個維度來保護服務的穩(wěn)定性。
核心分為兩部分:
1、核心庫(Java 客戶端):能夠運行在所有 Java 環(huán)境,對 Dubbo 、Spring Cloud 等框架也有較好的支持。
2、控制臺(Dashboard):基于 Spring Boot 開發(fā),打包后可以直接運行。
Sentinel 熔斷種類:
RT 響應時間
異常數(shù)
異常比例
Sentinel 安裝
首先,官網(wǎng)下載 sentinel 控制臺安裝包
下載地址:https://github.com/alibaba/Sentinel/releases
下載 Jar 包后,打開終端,運行命令
java?-Dserver.port=8180?-Dcsp.sentinel.dashboard.server=localhost:8180?-Dproject.name=sentinel-dashboard?-jar?sentinel-dashboard-1.8.1.jar登陸Sentinal控制臺:
默認用戶和密碼都是 sentinel ,登錄成功后的界面如下,先來個直觀感受
控制臺配置熔斷規(guī)則:
這里表示熔斷策略選擇 慢調(diào)用比例,響應時間超過200毫秒則標記為慢請求。如果在一個1000 ms的統(tǒng)計周期內(nèi)(可自行調(diào)整),慢請求比例超過30%且數(shù)量超過3個,則對后續(xù)請求進行熔斷,熔斷時長為10秒鐘,10秒以后恢復正常。
注解式接入
接入非常簡單,只需要提前在控制臺配置好資源規(guī)則,然后在代碼中添加 @SentinelResource注解即可。
//?資源名稱為handle1? @RequestMapping("/handle1") @SentinelResource(value?=?"handle1",?blockHandler?=?"blockHandlerTestHandler") public?String?handle1(String?params)?{?//?業(yè)務邏輯處理return?"success"; }//?接口方法?handle1?的?兜底方法 public?String?blockHandlerTestHandler(String?params,?BlockException?blockException)?{return?"兜底返回"; }達到閾值后,系統(tǒng)的默認提示是一段英文,很不友好,我們可以自定義兜底方法。在@SentinelResource注解中進一步配置 blockHandler、fallback 屬性字段
blockHandler:主觀層面,如果被限流或熔斷,則調(diào)用該方法,進行兜底處理
fallback:對業(yè)務的異常兜底,比如,執(zhí)行過程中拋了各種Exception,則調(diào)用該方法,進行兜底處理
通過上面兩層兜底,可以讓Sentinel 框架更加人性化,體驗更好。
注意:注解式開發(fā),需要添加在方法上,作用域范圍相對固定。下面的項目實戰(zhàn)中,我們也可以采用 顯示 形式,可以靈活圈定代碼塊范圍。
項目實戰(zhàn)
我們這邊有個項目,考慮到客戶的部署成本,想做一個輕量級方案,需求如下:
既想引入框架的熔斷功能,又不想部署控制臺
攔截點相對收攏,類似與dubbo消費端遠程訪問一樣,在代理類的遠程通訊位置做攔截處理
概要方案--流程圖:
1、我們通過 Proxy.newProxyInstance 為所有的接口創(chuàng)建了代理子類
2、所有對代理子類的方法調(diào)用全部收攏到 InvocationHandler
3、我們講類名和方法名做一個拼接,然后去 熔斷規(guī)則表查詢,看是否配置了規(guī)則
4、如果沒有,那么走常規(guī)則遠程調(diào)用邏輯
5、如果有,將遠程調(diào)用邏輯納入 Sentinel 的監(jiān)控管轄
6、如果觸發(fā)了 熔斷機制,則直接拋出 BlockException ,上層業(yè)務攔截異常,做特殊處理,比如:修飾下給用戶更合適的文案提示。
熔斷狀態(tài)機:
核心的代碼邏輯,繼續(xù)往下看
首先,引入 Sentinel 的依賴包:
<!--?限流、熔斷框架?--> <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>1.8.3</version> </dependency>熔斷規(guī)則表設計:
CREATE?TABLE?`degrade_rule`?(`id`?bigint?unsigned?NOT?NULL?AUTO_INCREMENT?COMMENT?'主鍵',`resource_name`?varchar(256)?NOT?NULL?COMMENT?'資源名稱',`count`?double?NOT?NULL?COMMENT?'慢調(diào)用時長,單位?毫秒',`slow_ratio_threshold`?double?NOT?NULL?COMMENT?'慢調(diào)用比例閾值',`min_request_amount`?int?NOT?NULL?COMMENT?'熔斷觸發(fā)的最小請求數(shù)',`stat_interval`?int?NOT?NULL?COMMENT?'統(tǒng)計時長,單位?毫秒',`time_window`?int?NOT?NULL?COMMENT?'熔斷時長,單位為?s',`created_time`?timestamp?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?COMMENT?'創(chuàng)建時間',`updated_time`?timestamp?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?ON?UPDATE?CURRENT_TIMESTAMP?COMMENT?'修改時間',PRIMARY?KEY?(`id`)?USING?BTREE,UNIQUE?KEY?`uk_resource_name`?(`resource_name`) )?ENGINE=InnoDB?AUTO_INCREMENT=1?DEFAULT?CHARSET=utf8mb3?COMMENT='熔斷規(guī)則表';由于放棄了部署控制臺,我們只能自己管理熔斷規(guī)則的各個屬性值。可以按企業(yè)內(nèi)部管理后臺風格,開發(fā)頁面管理這些規(guī)則。
當然,早期可以采用更簡單粗暴方式,在數(shù)據(jù)庫表手動初始化數(shù)據(jù)。如果要調(diào)整規(guī)則,走 SQL 訂正。
為了盡可能實時感知規(guī)則表數(shù)據(jù)變更,開發(fā)了定時任務,每 10 秒運行一次。
@Scheduled(cron?=?"0/10?*?*?*?*???") public?void?loadDegradeRule()?{List<DegradeRuleDO>?degradeRuleDOList?=?degradeRuleDao.queryAllRule();if?(CollectionUtils.isEmpty(degradeRuleDOList))?{return;}String?newMd5Hex?=?DigestUtils.md5Hex(JSON.toJSONString(degradeRuleDOList));if?(StringUtils.isBlank(newMd5Hex)?||?StringUtils.equals(lastMd5Hex,?newMd5Hex))?{return;}List<DegradeRule>?rules?=?null;List<String>?resourceNameList?=?new?ArrayList<>();rules?=?degradeRuleDOList.stream().map(degradeRuleDO?->?{//資源名,即規(guī)則的作用對象DegradeRule?rule?=?new?DegradeRule(degradeRuleDO.getResourceName())?//?熔斷策略,支持慢調(diào)用比例/異常比例/異常數(shù)策略.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())//慢調(diào)用比例模式下為慢調(diào)用臨界 RT(超出該值計為慢調(diào)用);異常比例/異常數(shù)模式下為對應的閾值.setCount(degradeRuleDO.getCount())//?熔斷時長,單位為?s.setTimeWindow(degradeRuleDO.getTimeWindow())//?慢調(diào)用比例閾值.setSlowRatioThreshold(degradeRuleDO.getSlowRatioThreshold())//熔斷觸發(fā)的最小請求數(shù),請求數(shù)小于該值時即使異常比率超出閾值也不會熔斷.setMinRequestAmount(degradeRuleDO.getMinRequestAmount())//統(tǒng)計時長(單位為?ms).setStatIntervalMs(degradeRuleDO.getStatInterval());resourceNameList.add(degradeRuleDO.getResourceName());return?rule;}).collect(Collectors.toList());if?(CollectionUtils.isNotEmpty(rules))?{DegradeRuleManager.loadRules(rules);ConsumerProxyFactory.resourceNameList?=?resourceNameList;lastMd5Hex?=?newMd5Hex;}log.error("[DegradeRuleConfig]?熔斷規(guī)則加載:?"?+?rules); }考慮到規(guī)則變更頻率不會很高,沒有必要每次都DegradeRuleManager.loadRules重新加載規(guī)則。這里設計了個小竅門
DigestUtils.md5Hex(JSON.toJSONString(degradeRuleDOList));
對查詢的規(guī)則內(nèi)容 JSON 序列化,然后計算其md5摘要,如果跟上一次的結(jié)果一致,說明這期間沒有變更,直接 return ,不做處理。
定義子類,實現(xiàn)了 InvocationHandler 接口。通過 Proxy.newProxyInstance 為目標接口創(chuàng)建一個代理子類。
這樣,每次調(diào)用接口方法,實際都是在調(diào)用 invoke 方法
@Override public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{Class<?>?clazz?=?proxy.getClass().getInterfaces()[0];String?urlCode?=?clazz.getName()?+?"#"?+?method.getName();if?(resourceNameList.contains(urlCode))?{//?增加熔斷處理Entry?entry?=?null;try?{entry?=?SphU.entry(urlCode);//?遠程網(wǎng)絡調(diào)用,獲取結(jié)果responseString?=?HttpClientUtil.postJsonRequest(url,?header,?body);}?catch?(BlockException?blockException)?{//?觸發(fā)熔斷l(xiāng)og.error("degrade?trigger?!??remote?url?:{}?",?urlCode);throw?new?DegradeBlockExcetion(urlCode);}?finally?{if?(entry?!=?null)?{entry.exit();}}?}?else?{//?常規(guī)處理,不走熔斷判斷邏輯//?省略}???? }實驗數(shù)據(jù):
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的阿里二面:外部接口大量超时,把整个系统拖垮,引发雪崩!如何解决?熔断......的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot thymeleaf
- 下一篇: 阿里最后一面,高并发下如何设计一个秒杀系