<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          阿里二面:外部接口大量超時(shí),把整個(gè)系統(tǒng)拖垮,引發(fā)雪崩!如何解決...

          共 10572字,需瀏覽 22分鐘

           ·

          2022-03-12 16:12

          大家好,我是Tom哥~

          互聯(lián)網(wǎng)+ 時(shí)代,業(yè)務(wù)數(shù)字化已經(jīng)蔓延到你能想到的各個(gè)行業(yè)。各種業(yè)務(wù)功能、營(yíng)銷玩法越來越多,系統(tǒng)也越來越復(fù)雜。

          面對(duì)不斷復(fù)雜的業(yè)務(wù)系統(tǒng),腦子越來越不夠用了

          69c3bce97defddd8100d633724f2ec8c.webp

          于是 聰明的人們 提出了 微服務(wù) 的設(shè)計(jì)思想

          本著 復(fù)雜的事情簡(jiǎn)單化 的原則,我們將一個(gè)大的系統(tǒng)拆分成若干個(gè)子系統(tǒng),每個(gè) 子系統(tǒng) 職責(zé)單一,按 DDD 的設(shè)計(jì)理念,承載一個(gè)子域的業(yè)務(wù)建設(shè)。

          于是,人們可以將精力聚焦,專心完成某一個(gè)業(yè)務(wù)點(diǎn)的深度建設(shè)。

          多個(gè)微服務(wù)系統(tǒng)之間通過 RPC 框架(如:dubbo、spring cloud、gRPC 等)完成了串聯(lián),但隨著調(diào)用量越來越大,人們發(fā)現(xiàn)服務(wù)與服務(wù)之間的穩(wěn)定性變得越來越重要

          31e80969caa7553b90bf408c42061c09.webp

          舉個(gè)例子:

          • Service D 掛了,響應(yīng)很慢
          • Service G 和 Service F ,都依賴 Service D,也會(huì)受到牽連,對(duì)外響應(yīng)也會(huì)變慢
          • 影響層層向上傳遞,Service A 和 Service B 也會(huì)被拖垮
          • 最后,引發(fā)雪崩效應(yīng),系統(tǒng)的故障影響面會(huì)越來越大

          為了解決這種問題,我們需要引入 熔斷 機(jī)制。“當(dāng)斷則斷,不受其亂。當(dāng)斷不斷,必受其難”

          什么是熔斷?

          熔斷,其實(shí)是對(duì)調(diào)用鏈路中某個(gè)資源出現(xiàn)不穩(wěn)定狀態(tài)時(shí)(如:調(diào)用超時(shí)或異常比例升高),對(duì)這個(gè)資源的調(diào)用進(jìn)行限制,讓請(qǐng)求快速失敗,避免影響到其它的資源而導(dǎo)致級(jí)聯(lián)錯(cuò)誤。

          當(dāng)資源被降級(jí)后,在接下來的降級(jí)時(shí)間窗口內(nèi),對(duì)該資源的調(diào)用都自動(dòng)熔斷(默認(rèn)是拋出 BlockException

          目前市面上的熔斷框架很多,如:Sentinel、Hystrix、Resilience4j 等,這些框架的設(shè)計(jì)理念都差不多。

          本文重點(diǎn)講下 Sentinel 是如何在項(xiàng)目中使用的

          Sentinel (分布式系統(tǒng)的流量防衛(wèi)兵) 是阿里開源的一套用于服務(wù)容錯(cuò)的綜合性解決方案。它以流量為切入點(diǎn), 從流量控制、熔斷降級(jí)、系統(tǒng)負(fù)載保護(hù)等多個(gè)維度來保護(hù)服務(wù)的穩(wěn)定性。

          核心分為兩部分:

          1、核心庫(Java 客戶端):能夠運(yùn)行在所有 Java 環(huán)境,對(duì) Dubbo 、Spring Cloud 等框架也有較好的支持。

          2、控制臺(tái)(Dashboard):基于 Spring Boot 開發(fā),打包后可以直接運(yùn)行。

          Sentinel 熔斷種類:

          • RT 響應(yīng)時(shí)間
          • 異常數(shù)
          • 異常比例

          Sentinel 安裝

          首先,官網(wǎng)下載 sentinel 控制臺(tái)安裝包

          下載地址:https://github.com/alibaba/Sentinel/releases

          下載 Jar 包后,打開終端,運(yùn)行命令

          java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar

          登陸Sentinal控制臺(tái):

          e27969484d0398b5a21c1f03c14dd752.webp


          默認(rèn)用戶和密碼都是 sentinel ,登錄成功后的界面如下,先來個(gè)直觀感受


          1631704ba3e387f2dea499aff3fa31fa.webp

          控制臺(tái)配置熔斷規(guī)則:

          1b17fcdc4aa4d7060f1995889632b36f.webp

          這里表示熔斷策略選擇 慢調(diào)用比例,響應(yīng)時(shí)間超過200毫秒則標(biāo)記為慢請(qǐng)求。如果在一個(gè)1000 ms的統(tǒng)計(jì)周期內(nèi)(可自行調(diào)整),慢請(qǐng)求比例超過30%且數(shù)量超過3個(gè),則對(duì)后續(xù)請(qǐng)求進(jìn)行熔斷,熔斷時(shí)長(zhǎng)為10秒鐘,10秒以后恢復(fù)正常。

          注解式接入

          接入非常簡(jiǎn)單,只需要提前在控制臺(tái)配置好資源規(guī)則,然后在代碼中添加 @SentinelResource注解即可。

          // 資源名稱為handle1 
          @RequestMapping("/handle1")
          @SentinelResource(value = "handle1", blockHandler = "blockHandlerTestHandler")
          public String handle1(String params) { 
              // 業(yè)務(wù)邏輯處理
              return "success";
          }

          // 接口方法 handle1 的 兜底方法
          public String blockHandlerTestHandler(String params, BlockException blockException) {
              return "兜底返回";
          }

          達(dá)到閾值后,系統(tǒng)的默認(rèn)提示是一段英文,很不友好,我們可以自定義兜底方法。在@SentinelResource注解中進(jìn)一步配置 blockHandler、fallback 屬性字段

          • blockHandler:主觀層面,如果被限流或熔斷,則調(diào)用該方法,進(jìn)行兜底處理
          • fallback:對(duì)業(yè)務(wù)的異常兜底,比如,執(zhí)行過程中拋了各種Exception,則調(diào)用該方法,進(jìn)行兜底處理

          通過上面兩層兜底,可以讓Sentinel 框架更加人性化,體驗(yàn)更好。

          注意:注解式開發(fā),需要添加在方法上,作用域范圍相對(duì)固定。下面的項(xiàng)目實(shí)戰(zhàn)中,我們也可以采用 顯示 形式,可以靈活圈定代碼塊范圍。

          項(xiàng)目實(shí)戰(zhàn)

          我們這邊有個(gè)項(xiàng)目,考慮到客戶的部署成本,想做一個(gè)輕量級(jí)方案,需求如下:

          • 既想引入框架的熔斷功能,又不想部署控制臺(tái)
          • 攔截點(diǎn)相對(duì)收攏,類似與dubbo消費(fèi)端遠(yuǎn)程訪問一樣,在代理類的遠(yuǎn)程通訊位置做攔截處理

          概要方案--流程圖:

          2de1a2c103fac61b3525bfc236c59b19.webp

          1、我們通過 Proxy.newProxyInstance 為所有的接口創(chuàng)建了代理子類

          2、所有對(duì)代理子類的方法調(diào)用全部收攏到 InvocationHandler

          3、我們講類名和方法名做一個(gè)拼接,然后去 熔斷規(guī)則表查詢,看是否配置了規(guī)則

          4、如果沒有,那么走常規(guī)則遠(yuǎn)程調(diào)用邏輯

          5、如果有,將遠(yuǎn)程調(diào)用邏輯納入 Sentinel 的監(jiān)控管轄

          6、如果觸發(fā)了 熔斷機(jī)制,則直接拋出 BlockException ,上層業(yè)務(wù)攔截異常,做特殊處理,比如:修飾下給用戶更合適的文案提示。

          熔斷狀態(tài)機(jī):

          69f91160accddeb728cdc65a82be9256.webp

          核心的代碼邏輯,繼續(xù)往下看

          首先,引入 Sentinel 的依賴包:

          <!-- 限流、熔斷框架 -->
          <dependency>
              <groupId>com.alibaba.csp</groupId>
              <artifactId>sentinel-core</artifactId>
              <version>1.8.3</version>
          </dependency>

          熔斷規(guī)則表設(shè)計(jì):

          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)用時(shí)長(zhǎng),單位 毫秒',
            `slow_ratio_threshold` double NOT NULL COMMENT '慢調(diào)用比例閾值',
            `min_request_amount` int NOT NULL COMMENT '熔斷觸發(fā)的最小請(qǐng)求數(shù)',
            `stat_interval` int NOT NULL COMMENT '統(tǒng)計(jì)時(shí)長(zhǎng),單位 毫秒',
            `time_window` int NOT NULL COMMENT '熔斷時(shí)長(zhǎng),單位為 s',
            `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
            `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間',
            PRIMARY KEY (`id`) USING BTREE,
            UNIQUE KEY `uk_resource_name` (`resource_name`)
          ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='熔斷規(guī)則表';

          由于放棄了部署控制臺(tái),我們只能自己管理熔斷規(guī)則的各個(gè)屬性值。可以按企業(yè)內(nèi)部管理后臺(tái)風(fēng)格,開發(fā)頁面管理這些規(guī)則。

          當(dāng)然,早期可以采用更簡(jiǎn)單粗暴方式,在數(shù)據(jù)庫表手動(dòng)初始化數(shù)據(jù)。如果要調(diào)整規(guī)則,走 SQL 訂正。

          為了盡可能實(shí)時(shí)感知規(guī)則表數(shù)據(jù)變更,開發(fā)了定時(shí)任務(wù),每 10 秒運(yùn)行一次。

          @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ī)則的作用對(duì)象
                  DegradeRule rule = new DegradeRule(degradeRuleDO.getResourceName()) 
                          // 熔斷策略,支持慢調(diào)用比例/異常比例/異常數(shù)策略
                          .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                          //慢調(diào)用比例模式下為慢調(diào)用臨界 RT(超出該值計(jì)為慢調(diào)用);異常比例/異常數(shù)模式下為對(duì)應(yīng)的閾值
                          .setCount(degradeRuleDO.getCount())
                          // 熔斷時(shí)長(zhǎng),單位為 s
                          .setTimeWindow(degradeRuleDO.getTimeWindow())
                          // 慢調(diào)用比例閾值
                          .setSlowRatioThreshold(degradeRuleDO.getSlowRatioThreshold())
                          //熔斷觸發(fā)的最小請(qǐng)求數(shù),請(qǐng)求數(shù)小于該值時(shí)即使異常比率超出閾值也不會(huì)熔斷
                          .setMinRequestAmount(degradeRuleDO.getMinRequestAmount())
                          //統(tǒng)計(jì)時(shí)長(zhǎ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ī)則變更頻率不會(huì)很高,沒有必要每次都DegradeRuleManager.loadRules重新加載規(guī)則。這里設(shè)計(jì)了個(gè)小竅門

          DigestUtils.md5Hex(JSON.toJSONString(degradeRuleDOList));

          對(duì)查詢的規(guī)則內(nèi)容 JSON 序列化,然后計(jì)算其md5摘要,如果跟上一次的結(jié)果一致,說明這期間沒有變更,直接 return ,不做處理。

          定義子類,實(shí)現(xiàn)了 InvocationHandler 接口。通過 Proxy.newProxyInstance 為目標(biāo)接口創(chuàng)建一個(gè)代理子類。

          這樣,每次調(diào)用接口方法,實(shí)際都是在調(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);
                      // 遠(yuǎn)程網(wǎng)絡(luò)調(diào)用,獲取結(jié)果
                      responseString = HttpClientUtil.postJsonRequest(url, header, body);
                  } catch (BlockException blockException) {
                      // 觸發(fā)熔斷
                      log.error("degrade trigger !  remote url :{} ", urlCode);
                      throw new DegradeBlockExcetion(urlCode);
                  } finally {
                      if (entry != null) {
                          entry.exit();
                      }
                  } 
               } else {
                    // 常規(guī)處理,不走熔斷判斷邏輯
                    // 省略
              }    
          }

          實(shí)驗(yàn)數(shù)據(jù):

          2824dba4da3f05c94ecdee3dadcf1d05.webp

          上面案例的源代碼已經(jīng)上傳到 GitHub,關(guān)注公眾號(hào):微觀技術(shù),回復(fù)關(guān)鍵詞:「1819」 即可獲取



          推薦閱讀:

          【萬字長(zhǎng)文】創(chuàng)業(yè)公司就應(yīng)該技術(shù)選型 Spring Cloud Alibaba

          Redis 使用 List 實(shí)現(xiàn)消息隊(duì)列的利與弊

          阿里一面:講一講各個(gè)Spring框架之間的關(guān)系

          如出一轍。。。


          歡迎關(guān)注微信公眾號(hào):互聯(lián)網(wǎng)全棧架構(gòu),收取更多有價(jià)值的信息。

          瀏覽 163
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产在视频线精品视频www666 | 日本黄色操逼视频 | 日韩欧美国产无码 | 亚洲欧美AA | 全国最大色综合网 |