<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>

          一次zuul版本升級(jí)產(chǎn)生的問題排查記錄

          共 2376字,需瀏覽 5分鐘

           ·

          2020-10-29 16:37

          起因

          事情的起因是由于早期的一些服務(wù)版本放到現(xiàn)在太低了,基本上都是 SpringBoot1.5.x ,因此準(zhǔn)備統(tǒng)一對(duì)服務(wù)進(jìn)行一次版本升級(jí),升級(jí)到 2.1.x , SpringCloud 版本升級(jí)到Greenwich。當(dāng)然我們用的舊版本的 zuul 相關(guān)的都需要升級(jí)。

          意外的 Bug

          我們網(wǎng)關(guān)使用的是 zuul,使用的是 spring-cloud-netflix 封裝的包,此次版本升級(jí)同步升級(jí)了相關(guān)的包。但是意外的情況發(fā)生了,在測(cè)試環(huán)境上我們發(fā)現(xiàn)上傳文件會(huì)出現(xiàn)異常。具體表現(xiàn)是這樣的:當(dāng)上傳的文件超出一定大小后,在經(jīng)過 zuul 網(wǎng)關(guān)并向其他服務(wù)轉(zhuǎn)發(fā)的時(shí)候,之前上傳的包就不見了。這個(gè)情況十分奇怪,因此馬上開始排查。

          Bug 的排查

          出現(xiàn)這樣的問題,第一反應(yīng)是測(cè)試是不是根本沒有上傳包所以當(dāng)然包沒法轉(zhuǎn)發(fā)到下一層,當(dāng)然這種想法很快被否定了。好吧,那就認(rèn)真的排查吧。

          首先先去追蹤了一下路由以及出現(xiàn)的具體日志,將問題定位到 zuul 服務(wù),排除了上游 nginx 和下游業(yè)務(wù)服務(wù)出現(xiàn)問題的可能。但是 zuul 服務(wù)沒有任何異常日志出現(xiàn),所以非常困擾。檢查過后發(fā)現(xiàn)文件確實(shí)有通過 zuul,但是之后憑空消失沒有留下一點(diǎn)痕跡。

          明明當(dāng)初考慮上傳文件的問題給 zuul 分配了兩個(gè) g 的內(nèi)存,怎么上傳 500m 的文件就出問題了呢?不對(duì)!此時(shí)我靈光一閃,會(huì)不會(huì)和垃圾回收機(jī)制有關(guān)。我們的文件是非常大的,這樣的大文件生成的大對(duì)象是會(huì)保存在 java 的堆上的,并且由于垃圾回收的機(jī)制,這樣的對(duì)象不會(huì)經(jīng)歷年輕代,會(huì)直接分配到老年代,會(huì)不會(huì)是由于我們內(nèi)存參數(shù)設(shè)置不合理導(dǎo)致老年代太小而放不下呢?想到做到,我們通過調(diào)整 jvm 參數(shù),保證了老年代至少有一個(gè) G 的空間,并且同步檢測(cè)了 java 的堆內(nèi)存的狀態(tài)。然而讓人失望的是居然沒有奏效。不過此時(shí)事情和開始不同,我們有了線索。在剛才的堆的內(nèi)存監(jiān)控中發(fā)現(xiàn)了一些異常,隨即合理懷疑是堆中內(nèi)存不夠?qū)е铝?oom。隨后加大內(nèi)存嘗試并且再次運(yùn)行,發(fā)現(xiàn)居然上傳成功了。果然是老年代內(nèi)存不足導(dǎo)致的 oom,不過雖然上傳成功,但是老年代中的內(nèi)存居然被占用了 1.6G 左右,明明是 500M 的文件,為什么會(huì)占用了這么大的內(nèi)存呢?

          雖然找到了原因,但是增加內(nèi)存顯然不是解決問題的方法,因此,我們?cè)趩?dòng)參數(shù)上新增了 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data 準(zhǔn)備查看 oom 的具體分析日志。

          image

          查看堆棧信息可以發(fā)現(xiàn),溢出是發(fā)生在 byte 數(shù)組的拷貝上,我們迅速定位代碼,可以找到如下的代碼:

          public?InputStream?getRequestEntity()?{
          if?(requestEntity?==?null)?{
          return?null;
          }

          if?(!retryable)?{
          return?requestEntity;
          }

          ????????try?{
          ????????????if?(!(requestEntity?instanceof?ResettableServletInputStreamWrapper))?{
          ????????????????requestEntity?=?new?ResettableServletInputStreamWrapper(
          ????????????????????????StreamUtils.copyToByteArray(requestEntity));
          ????????????}
          ????????????requestEntity.reset();
          ????????}
          ????????finally?{
          ????????????return?requestEntity;
          ????????}
          ????}

          這段代碼源自 RibbonCommandContext 是在 zuul 中進(jìn)行請(qǐng)求轉(zhuǎn)發(fā)的時(shí)候調(diào)用到的,具體的 OOM 是發(fā)生在調(diào)用 StreamUtils.copyToByteArray(requestEntity)); 的時(shí)候。繼續(xù)進(jìn)入方法查找源頭。最終經(jīng)過排查找到了溢出的源頭。ribbon 轉(zhuǎn)發(fā)中的用到了 ByteArrayOutputStream 的拷貝,代碼如下:

          public?synchronized?void?write(byte?b[],?int?off,?int?len)?{
          if?((off??b.length)?||?(len?((off?+?len)?-?b.length?>?0))?{
          throw?new?IndexOutOfBoundsException();
          }
          ensureCapacity(count?+?len);
          System.arraycopy(b,?off,?buf,?count,?len);
          count?+=?len;
          }
          可以看到這邊有一個(gè) ensureCapacity ,查看源碼:

          private?void?ensureCapacity(int?minCapacity)?{
          //?overflow-conscious?code
          if?(minCapacity?-?buf.length?>?0)
          grow(minCapacity);
          }

          ????private?void?grow(int?minCapacity)?{
          ????????//?overflow-conscious?code
          ????????int?oldCapacity?=?buf.length;
          ????????int?newCapacity?=?oldCapacity?<????????if?(newCapacity?-?minCapacity?????????????newCapacity?=?minCapacity;
          ????????if?(newCapacity?-?MAX_ARRAY_SIZE?>?0)
          ????????????newCapacity?=?hugeCapacity(minCapacity);
          ????????buf?=?Arrays.copyOf(buf,?newCapacity);
          ????}

          可以看到 ensureCapacity 做了一件事,就是當(dāng)流拷貝的時(shí)候 byte 數(shù)組的大小不夠了,那就調(diào)用 grow 進(jìn)行擴(kuò)容,而 grow 的擴(kuò)容和 ArrayList 不同,他的擴(kuò)容是每一次將數(shù)組擴(kuò)大兩倍。

          至此溢出的原因就很清楚了,500m 文件占用 1.6g 是因?yàn)閯偤糜|發(fā)擴(kuò)容,導(dǎo)致用了多一倍的空間來容納拷貝的文件,再加上源文件,所以占用了文件的 3 倍空間。

          解決方案

          至于解決方案,調(diào)整內(nèi)存占用或者是老年代的占比顯然不是合理的解決方案。我們?cè)倩仡^查看源代碼,可以看到這個(gè)部分

          if?(!retryable)?{
          return?requestEntity;
          }

          如果設(shè)置的不重試的話,那么 body 中的信息就不會(huì)被保存。所以,我們決定臨時(shí)先去除上傳文件涉及到的服務(wù)的重試,之后再修改上傳機(jī)制,在以后的上傳文件時(shí)繞過 zuul。

          追根溯源

          雖然找到的原因,并且也有了解決方案,但是我們?nèi)匀徊恢罏槭裁磁f版本是 ok 的,因此本著追根究底的態(tài)度,找到了舊版的 zuul 的源碼。

          新版的 ribbon 代碼集成 spring-cloud-netflix-ribbon ,而舊版的 ribbon 的代碼集成在 spring-cloud-netflix-core 中,所以稍稍花費(fèi)點(diǎn)時(shí)間才找到對(duì)應(yīng)的代碼,檢查不同,發(fā)現(xiàn)舊版的 getRequestEntity 沒有任何的處理,直接返回了 requestEntity

          public?InputStream?getRequestEntity()?{
          return?requestEntity;
          }

          而在之后的版本中馬上就加上了拷貝機(jī)制。于是我們?nèi)?github 上找到了當(dāng)初的那個(gè) commit

          之后我們順著 commit 中給出的信息找到了最初的 issue

          查看過 issue 之后發(fā)現(xiàn)這原來是舊版的一個(gè) bug,這個(gè) bug 會(huì)導(dǎo)致舊版的 post 請(qǐng)求在 retry 的時(shí)候有 body 丟失的情況,因此在新版本中進(jìn)行了修復(fù),當(dāng)請(qǐng)求為 post 的時(shí)候會(huì)對(duì)于 body 進(jìn)行緩存以便于重試。

          總結(jié)

          至此,我們?cè)颈镜膹?fù)原了這個(gè) bug 的全貌以及形成的歷史和原因。并且找到適當(dāng)?shù)慕鉀Q方案。最后提一句:真的不要用 zuul 來上傳大文件,真的會(huì)很糟糕!


          后臺(tái)回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝


          瀏覽 61
          點(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>
                  最新黄色免费视频 | 亚拍一区| 吖v在线视频免费观看免费观看 | 99热九九这里只有精品10 | 成人午夜av |