<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í)10宗罪。。。

          共 6396字,需瀏覽 13分鐘

           ·

          2023-01-12 14:22

          魚(yú)皮最新原創(chuàng)項(xiàng)目教程,歡迎學(xué)習(xí)

          大家好,我是魚(yú)皮。不知道你有沒(méi)有遇到過(guò)這樣的場(chǎng)景:我們提供的某個(gè) API 接口,響應(yīng)時(shí)間原本一直都很快,但在某個(gè)不經(jīng)意的時(shí)間點(diǎn),突然出現(xiàn)了接口超時(shí)。

          也許你會(huì)有點(diǎn)懵,到底是為什么呢?

          今天咱們來(lái)聊聊接口突然超時(shí)的 10 個(gè)原因,希望對(duì)你會(huì)有所幫助。

          1.網(wǎng)絡(luò)異常

          接口原本好好的,突然出現(xiàn)超時(shí),最常見(jiàn)的原因,可能是網(wǎng)絡(luò)出現(xiàn)異常了。比如:偶然的網(wǎng)絡(luò)抖動(dòng),或者是帶寬被占滿(mǎn)了。

          1.1 網(wǎng)絡(luò)抖動(dòng)

          經(jīng)常上網(wǎng)的我們,肯定遇到過(guò)這樣的場(chǎng)景:大多數(shù)情況下我們?cè)L問(wèn)某個(gè)網(wǎng)站很快,但偶爾會(huì)出現(xiàn)網(wǎng)頁(yè)一直轉(zhuǎn)圈,加載不出來(lái)的情況。

          有可能是你的網(wǎng)絡(luò)出現(xiàn)了抖動(dòng),丟包了。


          網(wǎng)頁(yè)請(qǐng)求 API 接口,或者接口返回?cái)?shù)據(jù)給網(wǎng)頁(yè),都有可能會(huì)出現(xiàn)網(wǎng)絡(luò)丟包的情況。

          網(wǎng)絡(luò)丟包可能會(huì)導(dǎo)致接口超時(shí)。

          2.1 帶寬被占滿(mǎn)

          有時(shí)候,由于頁(yè)面或者接口設(shè)計(jì)不合理,用戶(hù)請(qǐng)求量突增的時(shí)候,可能會(huì)導(dǎo)致服務(wù)器的網(wǎng)絡(luò)帶寬被占滿(mǎn)的情況。

          服務(wù)器帶寬指的是在一定時(shí)間內(nèi)傳輸數(shù)據(jù)的大小,比如:1秒傳輸了10M的數(shù)據(jù)。

          如果用戶(hù)請(qǐng)求量突然增多,超出了1秒10M的上限,比如:1秒100M,而服務(wù)器帶寬本身1秒就只能傳輸10M,這樣會(huì)導(dǎo)致在這1秒內(nèi),90M數(shù)據(jù)就會(huì)延遲傳輸?shù)那闆r,從而導(dǎo)致接口超時(shí)的發(fā)生。

          所以對(duì)于有些高并發(fā)請(qǐng)求場(chǎng)景,需要評(píng)估一下是否需要增加服務(wù)器帶寬。

          2.線程池滿(mǎn)了

          我們調(diào)用的API接口,有時(shí)候?yàn)榱诵阅芸紤],可能會(huì)使用線程池異步查詢(xún)數(shù)據(jù),最后把查詢(xún)結(jié)果進(jìn)行匯總,然后返回。

          如下圖所示:調(diào)用遠(yuǎn)程接口總耗時(shí) 200ms = 200ms(即耗時(shí)最長(zhǎng)的那次遠(yuǎn)程接口調(diào)用)

          在java8之前可以通過(guò)實(shí)現(xiàn)Callable接口,獲取線程返回結(jié)果。

          java8以后通過(guò)CompleteFuture類(lèi)實(shí)現(xiàn)該功能。我們這里以CompleteFuture為例:

          public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
              final UserInfo userInfo = new UserInfo();
              CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {
                  getRemoteUserAndFill(id, userInfo);
                  return Boolean.TRUE;
              }, executor);

              CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> {
                  getRemoteBonusAndFill(id, userInfo);
                  return Boolean.TRUE;
              }, executor);

              CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> {
                  getRemoteGrowthAndFill(id, userInfo);
                  return Boolean.TRUE;
              }, executor);
              CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();

              userFuture.get();
              bonusFuture.get();
              growthFuture.get();

              return userInfo;
          }

          這里我用到了executor,表示自定義的線程池,為了防止高并發(fā)場(chǎng)景下,出現(xiàn)線程過(guò)多的問(wèn)題。

          但如果用戶(hù)請(qǐng)求太多,線程池中已有的線程處理不過(guò)來(lái),線程池會(huì)把多余的請(qǐng)求,放到隊(duì)列排隊(duì),等待空閑線程去處理。

          如果隊(duì)列中排隊(duì)的任務(wù)非常多,某次 API 請(qǐng)求一直在等待,沒(méi)辦法得到及時(shí)處理,就會(huì)出現(xiàn)接口超時(shí)問(wèn)題。

          這時(shí)候,我們可以考慮是否核心線程數(shù)設(shè)置太小了,或者有多種業(yè)務(wù)場(chǎng)景共用了同一個(gè)線程池。

          如果是因?yàn)楹诵木€程池設(shè)置太小,可以將其調(diào)大一些。

          如果是因?yàn)槎喾N業(yè)務(wù)場(chǎng)景共用了同一個(gè)線程池,可以拆分成多個(gè)線程池

          3.數(shù)據(jù)庫(kù)死鎖

          有時(shí)候接口超時(shí)得有點(diǎn)莫名其妙,特別是遇到數(shù)據(jù)庫(kù)出現(xiàn)死鎖的時(shí)候。

          你提供的 API 接口中通過(guò)某個(gè) id 更新某條數(shù)據(jù),此時(shí),正好線上在手動(dòng)執(zhí)行一個(gè)批量更新數(shù)據(jù)的 sql 語(yǔ)句。

          該 sql 語(yǔ)句在一個(gè)事務(wù)當(dāng)中,并且剛好也在更新那條數(shù)據(jù),可能會(huì)出現(xiàn)死鎖的情況。

          由于該 sql 語(yǔ)句執(zhí)行時(shí)間很長(zhǎng),會(huì)導(dǎo)致 API 接口的那次更新數(shù)據(jù)操作,長(zhǎng)時(shí)間被數(shù)據(jù)庫(kù)鎖住,沒(méi)法及時(shí)返回?cái)?shù)據(jù),而出現(xiàn)接口超時(shí)問(wèn)題。

          你說(shuō)坑不坑?


          所以建議在執(zhí)行數(shù)據(jù)庫(kù)批量操作前,一定要評(píng)估數(shù)據(jù)的影響范圍,不要一次性更新太多的數(shù)據(jù),不然可能會(huì)導(dǎo)致很多意想不到的問(wèn)題。

          此外,批量更新操作建議在用戶(hù)訪問(wèn)少的時(shí)段執(zhí)行,比如:凌晨。

          4.傳入?yún)?shù)太多

          有時(shí)候,偶爾的一次接口超時(shí),是由于參數(shù)傳入太多導(dǎo)致的。

          例如:根據(jù) id 集合批量查詢(xún)分類(lèi)接口,如果傳入的 id 集合數(shù)據(jù)量不多,傳入幾十個(gè)或上百個(gè) id,不會(huì)出現(xiàn)性能問(wèn)題。畢竟 id 是分類(lèi)表的主鍵,可以走主鍵索引,數(shù)據(jù)庫(kù)的查找速度是非??斓摹?/p>

          但如果接口調(diào)用方,一次性傳入幾千個(gè),甚至幾萬(wàn)個(gè) id,批量查詢(xún)分類(lèi),也可能會(huì)出現(xiàn)接口超時(shí)問(wèn)題。

          因?yàn)閿?shù)據(jù)庫(kù)在執(zhí)行 sql 語(yǔ)句之前,會(huì)評(píng)估一下耗時(shí)情況,查詢(xún)條件太多,有可能走全表掃描更快。

          所以這種情況下 sql 語(yǔ)句可能會(huì)丟失索引,讓執(zhí)行時(shí)間變慢,出現(xiàn)接口超時(shí)問(wèn)題。

          因此我們?cè)谠O(shè)計(jì)批量接口的時(shí)候,建議要限制傳入的集合的大小,比如:500。

          如果超過(guò)我們?cè)O(shè)置最大的集合大小,則接口直接返回失敗,并提示給用戶(hù):一次性傳入?yún)?shù)過(guò)多。

          該限制一定要寫(xiě)到接口文檔中,避免接口調(diào)用方,在生產(chǎn)環(huán)境調(diào)用接口失敗而踩坑。要在接口開(kāi)發(fā)階段通知到位。

          此外,如果接口調(diào)用方要傳入的參數(shù)就是很多怎么辦?

          答:可能是需求不合理,或者系統(tǒng)設(shè)計(jì)有問(wèn)題,我們要盡量在系統(tǒng)設(shè)計(jì)階段就規(guī)避這個(gè)問(wèn)題。



          如果我們重新進(jìn)行系統(tǒng)設(shè)計(jì)改動(dòng)比較大的話,有個(gè)臨時(shí)的解決方案:在接口調(diào)用方中多線程分批調(diào)用該接口,最后將結(jié)果進(jìn)行匯總。

          5.超時(shí)時(shí)間設(shè)置過(guò)短

          通常情況下,建議我們?cè)谡{(diào)用遠(yuǎn)程 API 接口時(shí),要設(shè)置連接超時(shí)時(shí)間讀超時(shí)時(shí)間這兩個(gè)參數(shù),并且可以動(dòng)態(tài)配置。

          這樣做的好處是,可以防止調(diào)用遠(yuǎn)程 API 接口萬(wàn)一出現(xiàn)了性能問(wèn)題,響應(yīng)時(shí)間很長(zhǎng),把我們自己的服務(wù)拖掛的情況發(fā)生。

          比如:你調(diào)用的遠(yuǎn)程 API 接口,要 100 秒才返回?cái)?shù)據(jù),而你設(shè)置的超時(shí)時(shí)間是 100 秒。這時(shí) 1000 個(gè)請(qǐng)求過(guò)來(lái),去請(qǐng)求該API接口,這樣會(huì)導(dǎo)致tomcat線程池很快被占滿(mǎn),導(dǎo)致整個(gè)服務(wù)暫時(shí)不可用,至少新的請(qǐng)求過(guò)來(lái),是沒(méi)法即使響應(yīng)的。

          所以我們需要設(shè)置超時(shí)時(shí)間,并且超時(shí)時(shí)間還不能設(shè)置太長(zhǎng)。

          并發(fā)量不大的業(yè)務(wù)場(chǎng)景,可以將這兩個(gè)超時(shí)時(shí)間設(shè)置稍微長(zhǎng)一點(diǎn),比如:連接超時(shí)時(shí)間為10秒,讀超時(shí)時(shí)間為20秒。

          并發(fā)量大的業(yè)務(wù)場(chǎng)景,可以設(shè)置成秒級(jí)或者毫秒級(jí)。

          有些小伙伴為了開(kāi)發(fā)方便,在多種業(yè)務(wù)場(chǎng)景共用這兩個(gè)超時(shí)時(shí)間。

          某一天,在并發(fā)量大的業(yè)務(wù)場(chǎng)景中,你將該超時(shí)時(shí)間改短了。

          但直接導(dǎo)致并發(fā)量不大的業(yè)務(wù)場(chǎng)景中,出現(xiàn)調(diào)用API接口超時(shí)的問(wèn)題。

          因此,不建議多種業(yè)務(wù)場(chǎng)景共用同一個(gè)超時(shí)時(shí)間,最好根據(jù)并發(fā)量的不同,單獨(dú)設(shè)置不同的超時(shí)時(shí)間。

          6.一次性返回?cái)?shù)據(jù)太多

          不知道你有沒(méi)有遇到過(guò)這樣的需求:我們有個(gè) job,每天定時(shí)調(diào)用第三方 API 查詢(xún)接口,獲取昨天更新的數(shù)據(jù),然后更新到我們自己的數(shù)據(jù)庫(kù)表中。

          由于第三方每天更新的數(shù)據(jù)不多,所以該 API 接口響應(yīng)時(shí)間還是比較快的。

          但突然有一天,該 API 接口卻出現(xiàn)了接口超時(shí)問(wèn)題。

          查看日志發(fā)現(xiàn),該 API 接口一次性返回的數(shù)據(jù)太多,而且該數(shù)據(jù)的更新時(shí)間相同。

          這就可以斷定,該 API 接口提供方進(jìn)行了批量更新操作,修改了大量的數(shù)據(jù),導(dǎo)致該問(wèn)題的發(fā)生。

          即使我們?cè)?job 中加了失敗重試機(jī)制,但由于該API一次性返回?cái)?shù)據(jù)實(shí)在太多太多,重試也很有可能會(huì)接口超時(shí),這樣會(huì)導(dǎo)致一直獲取不到第三方前一天最新的數(shù)據(jù)。

          所以第三方這種根據(jù)日期查詢(xún)?cè)隽繑?shù)據(jù)的接口,建議做成分頁(yè)查詢(xún)的,不然后面沒(méi)準(zhǔn)哪一天,遇到批量更新的操作,就可能出現(xiàn)接口超時(shí)的問(wèn)題。

          7. 死循環(huán)

          死循環(huán)也會(huì)導(dǎo)致接口超時(shí)?

          死循環(huán)不應(yīng)該在接口測(cè)試階段就發(fā)現(xiàn)了,為什么要到生產(chǎn)環(huán)境才發(fā)現(xiàn)?



          確實(shí),絕大部分死循環(huán)問(wèn)題,在測(cè)試階段可以發(fā)現(xiàn)。

          但有些無(wú)限遞歸隱藏的比較深,比如下面的情況。

          死循環(huán)其實(shí)有兩種:

          1. 普通死循環(huán)
          2. 無(wú)限遞歸

          7.1 普通死循環(huán)

          有時(shí)候死循環(huán)是我們自己寫(xiě)的,例如下面這段代碼:

          while(true) {
              if(condition) {
                  break;
              }
              System.out.println("do samething");
          }

          這里使用了while(true)的循環(huán)調(diào)用,這種寫(xiě)法在CAS自旋鎖中使用比較多。

          當(dāng)滿(mǎn)足 condition 等于 true 的時(shí)候,則自動(dòng)退出該循環(huán)。

          如果 condition 條件非常復(fù)雜,一旦出現(xiàn)判斷不正確,或者少寫(xiě)了一些邏輯判斷,就可能在某些場(chǎng)景下出現(xiàn)死循環(huán)的問(wèn)題。

          出現(xiàn)死循環(huán),大概率是開(kāi)發(fā)人員人為的 bug 導(dǎo)致的,不過(guò)這種情況很容易被測(cè)出來(lái)。

          還有一種隱藏的比較深的死循環(huán),是由于代碼寫(xiě)的不太嚴(yán)謹(jǐn)導(dǎo)致的。如果用正常數(shù)據(jù),可能測(cè)不出問(wèn)題,但一旦出現(xiàn)異常數(shù)據(jù),就會(huì)立即出現(xiàn)死循環(huán)。

          7.2 無(wú)限遞歸

          如果想要打印某個(gè)分類(lèi)的所有父分類(lèi),可以用類(lèi)似這樣的遞歸方法實(shí)現(xiàn):

          public void printCategory(Category category) {
            if(category == null 
                || category.getParentId() == null) {
               return;
            } 
            System.out.println("父分類(lèi)名稱(chēng):"+ category.getName());
            Category parent = categoryMapper.getCategoryById(category.getParentId());
            printCategory(parent);
          }

          正常情況下,這段代碼是沒(méi)有問(wèn)題的。

          但如果某次有人誤操作,把某個(gè)分類(lèi)的 parentId 指向了它自己,這樣就會(huì)出現(xiàn)無(wú)限遞歸的情況。導(dǎo)致接口一直不能返回?cái)?shù)據(jù),最終會(huì)發(fā)生堆棧溢出。

          建議寫(xiě)遞歸方法時(shí),設(shè)定一個(gè)遞歸的深度,比如:分類(lèi)最大等級(jí)有 4 級(jí),則深度可以設(shè)置為 4。然后在遞歸方法中做判斷,如果深度大于 4 時(shí),則自動(dòng)返回,這樣就能避免無(wú)限遞歸的情況。

          8.sql語(yǔ)句沒(méi)走索引

          你有沒(méi)有遇到過(guò)這樣一種情況:明明是同一條 sql,只有入?yún)⒉煌?。有的時(shí)候走的索引 a,有的時(shí)候卻走的索引 b?

          沒(méi)錯(cuò),有時(shí)候 mysql 會(huì)選錯(cuò)索引,甚至有時(shí)會(huì)不走索引。

          mysql 在執(zhí)行某條 sql 語(yǔ)句之前,會(huì)通過(guò)抽樣統(tǒng)計(jì)來(lái)估算掃描行數(shù),根據(jù)影響行數(shù)、區(qū)分度、基數(shù)、數(shù)據(jù)頁(yè)等信息,最后綜合評(píng)估走哪個(gè)索引。

          有時(shí)候傳入?yún)?shù) 1,sql 語(yǔ)句走了索引 a,執(zhí)行時(shí)間很快。但有時(shí)候傳入?yún)?shù) 2,sql語(yǔ)句走了索引 b,執(zhí)行時(shí)間明顯慢了很多。

          這樣有可能會(huì)導(dǎo)致 API 接口出現(xiàn)超時(shí)問(wèn)題。

          必要時(shí)可以使用force index來(lái)強(qiáng)制查詢(xún)sql走某個(gè)索引。

          9.服務(wù)OOM

          我之前遇到過(guò)這樣一種場(chǎng)景:一個(gè)根據(jù) id 查詢(xún)分類(lèi)的接口,該 id 是主鍵, sql 語(yǔ)句可以走主鍵索引,竟然也出現(xiàn)了接口超時(shí)問(wèn)題。

          我當(dāng)時(shí)覺(jué)得有點(diǎn)不可思議,因?yàn)檫@個(gè)接口平均耗時(shí)只有十幾毫秒,怎么可能會(huì)出現(xiàn)超時(shí)呢?

          但從當(dāng)時(shí)的日志看,接口響應(yīng)時(shí)間有5秒,的確出現(xiàn)了接口超時(shí)問(wèn)題。

          最后從Prometheus的服務(wù)內(nèi)存監(jiān)控中,查到了OOM問(wèn)題。

          其實(shí)該API接口部署的服務(wù)當(dāng)時(shí)由于OOM內(nèi)存溢出,其實(shí)掛了一段時(shí)間。

          當(dāng)時(shí)所有的接口都出現(xiàn)了請(qǐng)求超時(shí)問(wèn)題。

          但由于K8S集群有監(jiān)控,它自動(dòng)會(huì)將掛掉的服務(wù)節(jié)點(diǎn)kill掉,并且在容器中重新部署了一個(gè)新的服務(wù)節(jié)點(diǎn),幸好對(duì)用戶(hù)沒(méi)造成太大的影響。。

          10.在debug

          我們有時(shí)候需要在本地開(kāi)發(fā)工具,比如:idea中,直接連接測(cè)試環(huán)境的數(shù)據(jù)庫(kù),調(diào)試某個(gè)API接口的業(yè)務(wù)邏輯。

          因?yàn)樵陂_(kāi)發(fā)環(huán)境,某些問(wèn)題不太好復(fù)現(xiàn)。

          為了排查某個(gè) bug,你在請(qǐng)求某個(gè)本地接口時(shí),開(kāi)啟了debug模式,一行行的跟蹤代碼,排查問(wèn)題。


          走到某一行代碼的時(shí)候,停留了很長(zhǎng)一段時(shí)間,該行代碼主要是更新某條數(shù)據(jù)。

          此時(shí),測(cè)試同學(xué)在相關(guān)的業(yè)務(wù)頁(yè)面中,操作更新了相同的數(shù)據(jù)。

          這種也可能會(huì)出現(xiàn)數(shù)據(jù)庫(kù)死鎖的問(wèn)題。

          由于你在 idea 的 debug 模式中,一直都沒(méi)有提交事務(wù),會(huì)導(dǎo)致死鎖的時(shí)間變得很長(zhǎng),從而導(dǎo)致業(yè)務(wù)頁(yè)面請(qǐng)求的 API 接口出現(xiàn)超時(shí)問(wèn)題。



          歡迎學(xué)編程的朋友們加入我的 
          編程知識(shí)星球 ,我會(huì) 1 對(duì) 1 解決你的問(wèn)題,直播帶你做出項(xiàng)目、為你定制學(xué)習(xí)計(jì)劃和求職指導(dǎo),還能獲取海量編程學(xué)習(xí)資源,和上萬(wàn)名學(xué)編程的同學(xué)共享知識(shí)、交流進(jìn)步。

          往期推薦

          我的學(xué)習(xí)小圈子

          又一個(gè)新項(xiàng)目搞完啦?。。?/p>

          寒假自學(xué)的小建議,彎道超車(chē)!

          這樣寫(xiě)SQL,同事說(shuō)我坑。。

          最適合程序員的畫(huà)圖工具?

          瀏覽 32
          點(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>
                  人人看人人草人人摸 | 人人操人人干人人操 | 粉嫩99精品99久久久久久桃色 | 一本色道久久综合无码人妻 | 色小姐综合网 |