誰的速度快!誰背鍋(技術(shù)解析)
本文轉(zhuǎn)載自微信公眾號「小姐姐味道」,作者小姐姐養(yǎng)的狗 。轉(zhuǎn)載本文請聯(lián)系小姐姐味道公眾號。
深夜,領(lǐng)導(dǎo):“你寫的接口有問題!趕緊起床瞧瞧”。
Ding!催命軟件一響,你就知道,該work了。
可思來想去,覺得不可能啊。我的代碼,就是一個簡單的redis查詢啊,難不成是Redis掛了?
同事把證據(jù)全部發(fā)到了群里,是你的接口無疑。一個簡單的Get查詢,平均耗時達到了2秒。jstack,promethus的監(jiān)控,把問題全部指向到了你的接口!
登錄Redis服務(wù)器,一切正常。該怎么辦?要這么不明不白不清不楚的背個章丘大鐵鍋么?
1. 快是原罪
這種情況下,要相信自己的直覺。你的接口又快又好,很可能是木秀于林,鶴立雞群,當(dāng)了替罪鳥。
在 “某些” "高并發(fā)"環(huán)境下,由于資源未做隔離,在發(fā)生問題的時候,一些日志和工具的表現(xiàn),會有非常強的迷惑性。
發(fā)生問題的,都是速度最快、請求最多的接口,但理論上并不可能。
如上圖。這種情況很常見。
大多數(shù)請求,通過Tomcat線程池的調(diào)度,進行真正的業(yè)務(wù)處理。當(dāng)然線程池是不干這種臟活的,它把請求交給資源處理池去處理,比如:
- 一個數(shù)據(jù)庫連接池,執(zhí)行耗時的統(tǒng)計操作和迅速的查詢操作
- 一個Redis連接池,執(zhí)行阻塞性的慢查詢和簡單的GET SET
- 一個Http連接池(HTTPClient、OkHTTP等),遠程調(diào)用速度不等的資源
- ...
我們平常的編碼中,通常都會共用這樣的資源池。因為它寫起代碼來簡單,不需要動腦。
但如果你的服務(wù)本身,并沒有做好拆分以及隔離,問題就是致命的。比如,你把報表接口和高并發(fā)的C端接口放在了一個實例上。
這時候,你就有可能被報表接口給坑了。
2. 一個例子
我們以數(shù)據(jù)庫連接池為例,來說明一下這個過程,先看一下以下基礎(chǔ)信息:
- Tomcat的連接池,配置大小為200個
- MySQL的連接池,配置大小為50個,算是比較大了
- 接口A需要調(diào)用耗時的查詢,耗時為5秒
- 接口B速度非??欤樵償?shù)據(jù)庫響應(yīng)時間在200ms以下
速度快的B接口,請求量是遠遠大于接口A的,平常情況下相安無事。
有一天,接口A忽然有了大量的查詢,由于它的耗時比較長,迅速把數(shù)據(jù)庫的50個連接池給占滿了(接口B由于響應(yīng)快,持有時間短,慢慢連接會被A吃掉)。
這時候,無論是接口A,還是接口B的請求,都需要等待至少5秒鐘,才能獲取下一條數(shù)據(jù)庫連接,業(yè)務(wù)才能正常走下去。
不一小會兒,服務(wù)的狀態(tài)就變成這樣:
- 數(shù)據(jù)庫連接池50個連接,迅速占滿,而且?guī)缀跞宦樵冋紳M
- Tomcat連接池的200個連接,迅速被占滿,其中大部分是速度快的接口B,因為它的請求量大速度快
- 所有接口都Block在Tomcat的線程上。進而造成:哪怕是查詢一個非數(shù)據(jù)庫的請求,也要等待5秒左右
一般在遇到這種問題的時候,我們都傾向于使用jstack打印信息堆棧,或者查看一些內(nèi)部的監(jiān)控曲線??上У氖?,這些信息,大部分都是騙人的,你看到的慢查詢,并不是真正的慢查詢。
從xjjdog上面的分析中,你應(yīng)該很容易看出問題的癥結(jié)所在:未隔離的瓶頸資源引起上游資源的連鎖反應(yīng)。
但在平常的工作中,xjjdog不止一次看到有同學(xué)對此手忙腳亂。很多證據(jù)都指向了一些又快又好的接口,而這些根本和它們一點關(guān)系都沒有。
他們樂呵呵的截圖,@相關(guān)人等,囂張至極。
在遇到這種情況的時候,你可以使用下面的腳本進行初步分析:
- $ 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上鎖的執(zhí)行棧,可以發(fā)現(xiàn)全部是卡在HttpClient的讀操作上了。在實際場景中,可以看下排行比較靠前的幾個鎖地址,找一下共性。
而這些顯示信息非常少的堆棧,才是問題的根本原因。
3. 如何解決
增加Tomcat連接池的大小,或者增加連接池的大小,并不能解決問題,大概率還會復(fù)現(xiàn)。
最好的解決方式,當(dāng)然是把耗時的服務(wù)和正常的服務(wù)拆分開來,比如時下流行的微服務(wù)。你的服務(wù)查詢慢,自己訪問超時,和我的服務(wù),一丁點兒關(guān)系都沒有。
但是,你的服務(wù)即然能遇到這種問題,就證明你的公司缺乏這種改造的條件。就只能在單體服務(wù)上來做文章。
這種做法,就是隔離。
如上圖,我們在同一個工程里,創(chuàng)建了兩個MySQL數(shù)據(jù)庫連接池,指向了相同的MySQL地址。使用這種方式,連接池的操作,就能夠相對做到互不影響。
但到現(xiàn)在為止,還沒完,因為你的Tomcat連接池依然是共享的。
慢查詢相關(guān)的,從連接池中獲取連接的策略,要改一下,不能一直等待,而應(yīng)該采用FailFast的方式(獲取連接短時間的超時也是可以的),否則癥狀還是一樣。
時下流行的熔斷概念,也在一定程度上實踐這種隔離性。
End
我們還可以聯(lián)想到類似的場景:
JVM發(fā)生STW,停頓期間,受影響最大的,就是那些又快請求又大的接口。而那些耗時接口,由于平常就是那個鳥樣,倒沒人關(guān)注它的異常情況。
一堆接口連接了同一個數(shù)據(jù)庫,當(dāng)數(shù)據(jù)庫發(fā)生抖動,受影響最大的,依然是那些又快請求又大的接口。因為那些耗時的慢查詢,一直就是那樣表現(xiàn)的,沒人會懷疑到它們身上來。
殊不知,只要這些爛接口請求量一上升,就會像一顆老鼠屎,壞了整鍋湯,所有的請求都會被拖累。
這有點類似于我們平常的工作:低效的人一增多,就會拖累整個項目的進度。領(lǐng)導(dǎo)一直在納悶,為什么那么多技術(shù)好手,效率那么低呢?
這是因為,他們被拖累了。過于關(guān)注個體,最根本性的問題卻掩蓋在表象之下。
公司內(nèi)部的研發(fā),從來不應(yīng)該一視同仁。不同技術(shù)追求的員工,也應(yīng)該做到類似的隔離,寧缺毋濫。
好手組成的團隊,交流順暢,目標(biāo)一致,效率奇高;而那些擅長拖慢項目的員工,就應(yīng)該放在低效的團隊,將加班進行到底。
說了這么多,問題的關(guān)鍵就在于:并不是每一個人都能了解這個規(guī)律,很少有人會關(guān)注這背后的根本原因。你要給領(lǐng)導(dǎo)解釋你的接口沒有問題,需要花費很大的力氣。
“老板,我找到原因了。是因為一個MySQL慢查詢,把Tomcat的連接池占滿了,造成了Redis對應(yīng)的Http請求響應(yīng)慢。”這樣錯綜復(fù)雜的關(guān)系,真的讓人很頭痛。
“很好”,領(lǐng)導(dǎo)說,“這個問題,就有你牽頭來解決一下吧”。
你瞧,做領(lǐng)導(dǎo)的,大多不會關(guān)注問題產(chǎn)生的原因,他只關(guān)注誰能解決這個問題,哪怕不是你的問題。誰讓你代碼寫得好,需求又做的快呢!
作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個人微信xjjdog0,歡迎添加好友,進一步交流。