<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      生產(chǎn)事故-Caffeine緩存誤用之臨下班的救贖

      入職多年,面對生產(chǎn)環(huán)境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統(tǒng)停擺,損失資金。每一個生產(chǎn)事故的背后,都是寶貴的經(jīng)驗和教訓(xùn),都是項目成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經(jīng)歷,有些是經(jīng)人耳傳口授,但無一例外都是真實案例。

      注意:為了避免不必要的麻煩和商密問題,文中提到的特定名稱都將是化名、代稱。

      0x00 大綱

      0x01 事故背景

      2025年7月9日17時有余,筆者正準(zhǔn)備結(jié)束疲憊的一天,關(guān)機(jī)走人之時,桌面右下角安靜了一天的內(nèi)部通訊軟件圖標(biāo)突然亮起,內(nèi)心頓感不妙……打開一看,原來是運維小哥找過來了,說是某接口服務(wù)連續(xù)多次調(diào)用超時或失敗,觸發(fā)告警閾值,具體原因不明,請求支援。(臨下班出事似乎已成為一種規(guī)律)

      0x02 事故分析

      該服務(wù)是一個基于 SpringBoot + JDK 1.8 的 API 服務(wù),提供了幾個信息查詢接口,沒有復(fù)雜的業(yè)務(wù)邏輯,也不涉及第三方接口調(diào)用,僅依賴于數(shù)據(jù)庫進(jìn)行簡單的 CURD 操作。

      第一時間讓運維拷貝和固定了事故系統(tǒng)日志及生產(chǎn)版本應(yīng)用包。發(fā)現(xiàn)該服務(wù)在一周前升級部署過,不排除是版本升級引起的問題。于是先留一手,招呼運維小哥做好隨時進(jìn)行版本回退的準(zhǔn)備,以免不能及時修復(fù)問題。

      運維小哥已經(jīng)排除了是網(wǎng)絡(luò)和線路的問題,也嘗試按照常見故障應(yīng)對手冊重啟過應(yīng)用,服務(wù)短暫恢復(fù)正常,但是隨著請求壓力上來以后,又會頻繁失敗觸發(fā)告警。為了避免事故進(jìn)一步擴(kuò)大,運維小哥選擇迅速搖人。

      秉著先易后難的順序,先快速掃描了一遍應(yīng)用日志,常規(guī)日志未見明顯ERROR、WARN以及Exception等信息,SQL日志未見慢查詢和連接池異常。隨后檢查數(shù)據(jù)庫壓力,發(fā)現(xiàn)數(shù)據(jù)庫活躍連接數(shù)不高,也未見死鎖和異常會話。

      jps找到服務(wù)進(jìn)程對應(yīng)的PID,使用top命令查看進(jìn)程的資源占用情況,發(fā)現(xiàn)服務(wù)的 CPU 和內(nèi)存資源占用不高。ss -antp|grep :9999| wc -l查看對應(yīng)端口的連接情況,大約兩百多個活躍連接,屬于正常范圍內(nèi)。磁盤監(jiān)控未見明顯壓力,看來基本可以確定是應(yīng)用本身的問題。

      于是使用jstack -l保存了第一次線程快照,然后讓運維小哥重啟接口服務(wù),果然如小哥所說,接口調(diào)用短暫正常以后很快又出現(xiàn)異常。為了排除偶然因素干擾,這時做了第二次線程快照用于對照分析,同時使用jmap抓取了 dump 文件備用。完成以上步驟以后,果斷讓運維小哥將服務(wù)回退到歷史版本,應(yīng)急解決故障。

      仔細(xì)分析兩次抓取的線程快照,發(fā)現(xiàn)大量的線程處于BLOCKED狀態(tài),且擁有高度相似的調(diào)用棧:

      "thread-3197" Id=4959 BLOCKED on java.util.concurrent.ConcurrentHashMap$ReservationNode@1b1f101f owned by "TaskExecutor-827" Id=936
      	at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1868)
      	-  blocked on java.util.concurrent.ConcurrentHashMap$ReservationNode@1b1f101f
      	at com.github.benmanes.caffeine.cache.BoundedLocalCache.doComputeIfAbsent(BoundedLocalCache.java:2404)
      	at com.github.benmanes.caffeine.cache.BoundedLocalCache.computeIfAbsent(BoundedLocalCache.java:2387)
      	at com.github.benmanes.caffeine.cache.LocalCache.computeIfAbsent(LocalCache.java:108)
      	at com.github.benmanes.caffeine.cache.LocalLoadingCache.get(LocalLoadingCache.java:56)
      	(這里省略部分信息)
      

      看起來是高并發(fā)的時候 Caffeine 緩存的處理出現(xiàn)了競態(tài)爭搶,問題初步定位,需要進(jìn)一步分析事故原因。

      0x03 事故原因

      簡單 review 了一下變更的代碼,發(fā)現(xiàn)同事A為某個關(guān)鍵系統(tǒng)參數(shù)的查詢添加了秒級的短時緩存,減少高并發(fā)下的數(shù)據(jù)庫查詢調(diào)用,并且使用有界的LoadingCache來加載和刷新相關(guān)數(shù)據(jù),關(guān)鍵的Bean定義如下:

      @Bean
      @ConditionalOnBean(ParameterRepository.class)
      public LoadingCache<String, ParameterEntity> parameterCache(ParameterRepository parameterRepository,
                                                                  Executor refreshExecutor) {
          return Caffeine.newBuilder()
                  .maximumSize(256)
                  .refreshAfterWrite(Duration.ofSeconds(3))
                  .expireAfterAccess(Duration.ofSeconds(7))
                  .executor(refreshExecutor)
                  .build(bssSysparmRepository::getById);
      }
      

      乍看之下似乎很合理。但是為何會出問題呢?在高并發(fā)場景下,多個線程同時請求緩存中不存在的數(shù)據(jù),導(dǎo)致多個線程都需要去加載數(shù)據(jù),而LoadingCache的刷新策略是按需刷新,即只有當(dāng)緩存中的數(shù)據(jù)過期時才會觸發(fā)刷新。如果多個線程同時觸發(fā)刷新,就會導(dǎo)致多個線程同時去加載數(shù)據(jù),并使用相同的Key值調(diào)用ConcurrentHashMap.compute方法加載和刷新數(shù)據(jù),從而導(dǎo)致競態(tài)爭搶。這種機(jī)理導(dǎo)致LoadingCache或者說ConcurrentHashMap(在JDK1.8里面)并不適合用在需要高并發(fā)頻繁刷新的緩存場景。

      有意思的是,這個鍋其實跟JDKConcurrentHashMap的實現(xiàn)機(jī)制有關(guān),存在同樣問題的還有computeIfPresent方法,具體可見。

      解決的方法不難,就是使用AsyncLoadingCache來代替LoadingCache,異步加載數(shù)據(jù),避免競態(tài)爭搶。修改下代碼:

      @Bean
      @ConditionalOnBean(ParameterRepository.class)
      public AsyncLoadingCache<String, ParameterEntity> parameterCache(ParameterRepository parameterRepository,
                                                                       Executor refreshExecutor) {
          return Caffeine.newBuilder()
                  .maximumSize(256)
                  .refreshAfterWrite(Duration.ofSeconds(3))
                  .expireAfterAccess(Duration.ofSeconds(7))
                  .executor(refreshExecutor)
                  .buildAsync(parameterRepository::getById);
      }
      

      取用時,從LoadingCache.get()方法改為AsyncLoadingCache.synchronous().get()方法即可。優(yōu)化版本上線后,各方人員情緒穩(wěn)定。

      0x04 事故復(fù)盤

      比起追究責(zé)任,更重要的是帶給我們的啟發(fā):

      • 沒有基準(zhǔn)的性能優(yōu)化都是耍流氓;
      • 上線前需要先進(jìn)行性能回歸,確認(rèn)優(yōu)化后的性能是否符合預(yù)期。

      0x05 事故影響

      生產(chǎn)系統(tǒng)緊急回滾一次,所幸的是沒有收到客訴(那就是沒有問題)。A公司相關(guān)負(fù)責(zé)人連夜編寫事故報告一份,并通知其他項目組未雨綢繆。

      posted @ 2025-10-24 15:05  程語有云  閱讀(1272)  評論(9)    收藏  舉報
      主站蜘蛛池模板: 亚洲精品国产中文字幕| 国产在线98福利播放视频| 日韩一区二区三区理伦片 | 在线日韩日本国产亚洲| 少妇精品无码一区二区免费视频| 91精品国产免费人成网站| 国产不卡一区二区在线| 中文字幕日韩国产精品| 国产精品中文字幕在线| 狠狠色丁香婷婷综合尤物| 国产视频深夜在线观看| 日韩精品一区二区三区激情视频 | 午夜免费福利小电影| 开心五月婷婷综合网站| 少妇人妻真实偷人精品| 精品无码老熟妇magnet| 天天躁日日躁狠狠躁中文字幕 | 国产精品老熟女一区二区| 久久人妻无码一区二区| 国产爆乳无码av在线播放| 亚洲中文字幕乱码一区| 国产精品亚洲综合一区二区| 国产免费无遮挡吸乳视频在线观看| 精品九九人人做人人爱| 亚洲熟妇少妇任你躁在线观看无码| 华人在线亚洲欧美精品| 无码 人妻 在线 视频| 精品自拍自产一区二区三区| 丰满的女邻居2| 新巴尔虎右旗| 亚洲综合色区另类av| 亚洲大尺度一区二区av| 人妻无码vs中文字幕久久av爆| 国产av午夜精品福利| 免费无遮挡毛片中文字幕| 亚洲日韩性欧美中文字幕| 99国产欧美另类久久久精品| 国产精品视频午夜福利| 精品国产中文字幕在线看| 99久久婷婷国产综合精品青草漫画| 精品午夜福利在线视在亚洲|