1. 程式人生 > 實用技巧 >誰的速度快!誰背鍋(技術解析)

誰的速度快!誰背鍋(技術解析)

溫馨提示:如果你沒有相關的從業經驗,本文將會非常晦澀。

深夜,領導: “你寫的介面有問題!趕緊起床瞧瞧”

Ding!催命軟體一響,你就知道,該work了。

可思來想去,覺得不可能啊。我的程式碼,就是一個簡單的redis查詢啊,難不成是Redis掛了?

同事把證據全部發到了群裡,是你的介面無疑。一個簡單的Get查詢,平均耗時達到了2秒。jstack,promethus的監控,把問題全部指向到了你的介面!

登入Redis伺服器,一切正常。該怎麼辦?要這麼不明不白不清不楚的背個章丘大鐵鍋麼?

1. 快是原罪

這種情況下,要相信自己的直覺。你的介面又快又好,很可能是木秀於林,鶴立雞群,當了替罪鳥。

在 “某些” "高併發"環境下,由於資源未做隔離,在發生問題的時候,一些日誌和工具的表現,會有非常強的迷惑性。

發生問題的,都是速度最快、請求最多的介面,但理論上並不可能。

如上圖。這種情況很常見。

大多數請求,通過Tomcat執行緒池的排程,進行真正的業務處理。當然執行緒池是不幹這種髒活的,它把請求交給資源處理池去處理,比如:

  1. 一個數據庫連線池,執行耗時的統計操作迅速的查詢操作
  2. 一個Redis連線池,執行阻塞性的慢查詢簡單的GET SET
  3. 一個Http連線池(HTTPClient、OkHTTP等),遠端呼叫速度不等的資源
  • ...

我們平常的編碼中,通常都會共用這樣的資源池。因為它寫起程式碼來簡單,不需要動腦。

但如果你的服務本身,並沒有做好拆分以及隔離,問題就是致命的。比如,你把報表介面和高併發的C端介面放在了一個例項上。

這時候,你就有可能被報表介面給坑了。

2. 一個例子

我們以資料庫連線池為例,來說明一下這個過程,先看一下以下基礎資訊:

  • Tomcat的連線池,配置大小為200
  • MySQL的連線池,配置大小為50個,算是比較大了
  • 介面A需要呼叫耗時的查詢,耗時為5秒
  • 介面B速度非常快,查詢資料庫響應時間在200ms以下

速度快的B介面,請求量是遠遠大於介面A的,平常情況下相安無事。

有一天,介面A忽然有了大量的查詢,由於它的耗時比較長,迅速把資料庫的50個連線池給佔滿了(介面B由於響應快,持有時間短,慢慢連線會被A吃掉)。

這時候,無論是介面A,還是介面B的請求,都需要等待至少5秒鐘,才能獲取下一條資料庫連線,業務才能正常走下去。

不一小會兒,服務的狀態就變成這樣:

  • 資料庫連線池50個連線,迅速佔滿,而且幾乎全被慢查詢佔滿
  • Tomcat連線池的200個連線,迅速被佔滿,其中大部分是速度快的介面B,因為它的請求量大速度快
  • 所有介面都Block在Tomcat的執行緒上。進而造成:哪怕是查詢一個非資料庫的請求,也要等待5秒左右

一般在遇到這種問題的時候,我們都傾向於使用jstack列印資訊堆疊,或者檢視一些內部的監控曲線。可惜的是,這些資訊,大部分都是騙人的,你看到的慢查詢,並不是真正的慢查詢。

從xjjdog上面的分析中,你應該很容易看出問題的癥結所在:未隔離的瓶頸資源引起上游資源的連鎖反應。

但在平常的工作中,xjjdog不止一次看到有同學對此手忙腳亂。很多證據都指向了一些又快又好的介面,而這些根本和它們一點關係都沒有。

他們樂呵呵的截圖,@相關人等,囂張至極。

在遇到這種情況的時候,你可以使用下面的指令碼進行初步分析:

$ cat 10271.tdump| grep "waiting to lock " | awk '{print $5}' | sort | uniq -c | sort -k1 -r

26 <0x0000000782e1b590>
  18 <0x0000000787b00448>
  16 <0x0000000787b38128>
  10 <0x0000000787b14558>
複製程式碼

上面的例子,我們找到給0x0000000782e1b590上鎖的執行棧,可以發現全部是卡在HttpClient的讀操作上了。在實際場景中,可以看下排行比較靠前的幾個鎖地址,找一下共性。

而這些顯示資訊非常少的堆疊,才是問題的根本原因。

3. 如何解決

增加Tomcat連線池的大小,或者增加連線池的大小,並不能解決問題,大概率還會復現。

最好的解決方式,當然是把耗時的服務和正常的服務拆分開來,比如時下流行的微服務。你的服務查詢慢,自己訪問超時,和我的服務,一丁點兒關係都沒有。

但是,你的服務即然能遇到這種問題,就證明你的公司缺乏這種改造的條件。就只能在單體服務上來做文章。

這種做法,就是隔離。

如上圖,我們在同一個工程裡,建立了兩個MySQL資料庫連線池,指向了相同的MySQL地址。使用這種方式,連線池的操作,就能夠相對做到互不影響。

但到現在為止,還沒完,因為你的Tomcat連線池依然是共享的。

慢查詢相關的,從連線池中獲取連線的策略,要改一下,不能一直等待,而應該採用FailFast的方式(獲取連線短時間的超時也是可以的),否則症狀還是一樣。

時下流行的熔斷概念,也在一定程度上實踐這種隔離性。

End

我們還可以聯想到類似的場景:

JVM發生STW,停頓期間,受影響最大的,就是那些又快請求又大的介面。而那些耗時介面,由於平常就是那個鳥樣,倒沒人關注它的異常情況。

一堆介面連線了同一個資料庫,當資料庫發生抖動,受影響最大的,依然是那些又快請求又大的介面。因為那些耗時的慢查詢,一直就是那樣表現的,沒人會懷疑到它們身上來。

殊不知,只要這些爛介面請求量一上升,就會像一顆老鼠屎,壞了整鍋湯,所有的請求都會被拖累。

這有點類似於我們平常的工作:低效的人一增多,就會拖累整個專案的進度。領導一直在納悶,為什麼那麼多技術好手,效率那麼低呢?

這是因為,他們被拖累了。過於關注個體,最根本性的問題卻掩蓋在表象之下。

公司內部的研發,從來不應該一視同仁。不同技術追求的員工,也應該做到類似的隔離,寧缺毋濫。

好手組成的團隊,交流順暢,目標一致,效率奇高;而那些擅長拖慢專案的員工,就應該放在低效的團隊,將加班進行到底。

說了這麼多,問題的關鍵就在於:並不是每一個人都能瞭解這個規律,很少有人會關注這背後的根本原因。你要給領導解釋你的介面沒有問題,需要花費很大的力氣。

“老闆,我找到原因了。是因為一個MySQL慢查詢,把Tomcat的連線池佔滿了,造成了Redis對應的Http請求響應慢。”這樣錯綜複雜的關係,真的讓人很頭痛。

“很好”,領導說,“這個問題,就有你牽頭來解決一下吧”。

你瞧,做領導的,大多不會關注問題產生的原因,他只關注誰能解決這個問題,哪怕不是你的問題。誰讓你程式碼寫得好,需求又做的快呢!


作者:小姐姐味道
連結:https://juejin.cn/post/6901541220661936136