1. 程式人生 > >每秒上萬並發下的Spring Cloud參數優化實戰

每秒上萬並發下的Spring Cloud參數優化實戰

一輪 再看 程序 系統表 技術分享 業務流程 自動 src 主從

一、寫在前面

相信不少朋友都在自己公司使用Spring Cloud框架來構建微服務架構,畢竟現在這是非常火的一門技術。

如果只是用戶量很少的傳統IT系統,使用Spring Cloud可能還暴露不出什麽問題。

如果是較多用戶量,高峰每秒高達上萬並發請求的互聯網公司的系統,使用Spring Cloud技術就有一些問題需要註意了。

二、場景引入,問題初現

先不空聊原理、理論,來講一個真實的例子,這是我的一個朋友在創業互聯網公司發生過的真實案例。



朋友A的公司做互聯網類的創業,組建了一個小型研發團隊,上來就用了Spring Cloud技術棧來構建微服務架構的系統。

一段時間沒日沒夜的加班,好不容易核心業務系統給做出來了,平時正常QA測試沒發現什麽大毛病,感覺性能還不錯,一切都很完美。

然後系統就這麽上線了,一開始用戶規模很小,註冊用戶量小幾十萬,日活幾千用戶。

每天都有新的數據進入數據庫的表中,就這麽日積月累的,沒想到數據規模居然慢慢吞吞增長到了單表幾百萬。

這個時候呢,看起來也沒太大的毛病,就是有用戶反映,系統有些操作,會感覺卡頓幾秒鐘,會刷不出來頁面。

這是為啥呢?

  • 核心原因是單表數據量大了一些,達到了幾百萬。

  • 有個別服務,跑的SQL比較復雜,一大堆的多表關聯

  • 並且還沒有設計好索引,或者是設計了索引,但無奈一些小弟寫了上百行的大SQL,SQL實在太復雜了,那麽一個SQL跑出來好幾秒肯定是正常的。

如果大家對微服務框架有點了解的話,應該知道,比如Feign + Ribbon組成的服務調用框架,是有接口調用超時這一說的,有一些參數可以設置接口調用的超時時間。

如果你調用一個接口,好幾秒刷不出來,人家就超時異常返回,用戶就刷不出來頁面了。

三、揚湯止沸,飲鴆止渴

一般碰到這種事情,一大坨屎一樣的SQL擺在那兒,寫SQL的人過一個月自己都看不懂了,80%的工程師看著都不願意去花時間重寫和優化。

一是修改的人力成本太高,二是誰敢負擔這責任呢?

系統跑的好好的,就是慢了點而已,結果你硬是亂改一通,重構,把系統核心業務流程搞掛了怎麽辦?

所以,那些兄弟第一反應是:增加超時時間啊!接口慢點可以,但是別超時不響應啊!

咱們讓接口執行個幾秒把結果返回,用戶不就可以刷出來頁面了!不用重構系統了啊!輕松+愉快!

如何增加呢?很簡單,看下面的參數就知道了:

技術分享圖片

大家如果看過之前的文章,應該知道,Spring Cloud裏一般會用hystrix的線程池來執行接口調用的請求。

如果忘了這一點的,可以回頭看看《拜托,面試請不要再問我Spring Cloud底層原理!》。

所以設置超時一般設置兩個地方,feign和ribbon那塊的超時,還有hystrix那塊的超時。其中後者那塊的超時一般必須大於前者。

Spring Cloud玩兒的好的兄弟,可千萬別看著這些配置發笑,因為我確實見過不少Spring Cloud玩兒的沒那麽溜的哥們,真的就這麽幹了。

好了,日子在繼續。。。

優化了參數後,看上去效果不錯,用戶雖然覺得有的頁面慢是慢點,但是起碼過幾秒能刷出來。

這個時候,日活幾千的用戶量,壓根兒沒什麽並發可言,高峰期每秒最多一二十並發請求罷了。

大家看看下面這張圖,感受一下現場氛圍:

技術分享圖片

四、問題爆發,洪水猛獸

隨著時間的推移,公司業務高速發展……

那位兄弟的公司,在系統打磨成熟,幾萬用戶試點都ok之後,老板立馬拿到一輪幾千萬的融資。

公司上上下下意氣風發啊!緊接著就是組建運營團隊,地推團隊,全國大範圍的推廣。

總之就是三個字推!推!推!

這一推不打緊!研發人員在後臺系統發現,自己的用戶量蹭蹭蹭的直線增長。

註冊用戶增長了幾十倍,突破了千萬級別,日活用戶也翻了幾十倍,在活動之類的高峰期,居然達到了上百萬的日活用戶量!

幸福的煩惱。。。

為什麽這麽說?因為用戶量上來後,悲劇的事情就發生了。

高峰期每秒的並發請求居然達到了近萬的程度,研發團隊的兄弟們哪裏敢怠慢!在這個過程中,先是緊張的各種擴容服務,一臺變兩臺,兩臺變四臺。

然後數據庫主從架構掛上去,讀寫分離是必須的,否則單個數據庫服務器哪能承載那麽大的請求!多搞幾個從庫,扛一下大量的讀請求,這樣基本就扛住了。

正準備坐下來喝口茶、松口氣,更加悲劇的事情就發生了。

在這個過程中,那些兄弟經常會發現高峰期,系統的某個功能頁面,突然就整個hang死了,就是沒法再響應任何請求!所有用戶刷新這個頁面全部都是無法響應!

這是為什麽呢?原因很簡單啊!一個服務A的實例裏,專門調用服務B的那個線程池裏的線程,總共可能就幾十個。每個線程調用服務B都會卡住5秒鐘。

那如果每秒鐘過來幾百個請求這個服務實例呢?一下子那個線程池裏的線程就全部hang死了,沒法再響應任何請求了。

大家來看看下面這張圖,再直觀的感受一下這個無助的過程!

技術分享圖片

這個時候咋辦?兄弟們只能祭出程序員最古老的法寶,重啟機器!

遇到頁面刷不出來,只能重啟機器,相當於短暫的初始化了一下機器內的資源。

然後接著運行一段時間,又卡死,再次重啟!真是令人崩潰啊!用戶們的體驗是極差的,老板的心情是憤怒的!

畫外音:

其實這個問題本身不大,但如果對Spring Cloud沒有高並發場景的真實經驗,確實可能會跟這幫兄弟一樣,搞出些莫名其妙的問題。

比如這個公司,明明應該去優化服務接口性能,結果硬是調大了超時時間。結果導致並發量高了,對那個服務的調用直接hang死,系統的核心頁面刷不出來,影響用戶體驗了,這怪誰呢?

五、追本溯源,治標治本

沒法子了,那幫兄弟們只能找人求助。下面就是作者全程指導他們完成系統優化的過程。

第一步

關鍵點,優化圖中核心服務B的性能。互聯網公司,核心業務邏輯,面向C端用戶高並發的請求,不要用上百行的大SQL,多表關聯,那樣單表幾百萬行數據量的話,會導致一下執行好幾秒。

其實最佳的方式,就是對數據庫就執行簡單的單表查詢和更新,然後復雜的業務邏輯全部放在java系統中來執行,比如一些關聯,或者是計算之類的工作。

這一步幹完了之後,那個核心服務B的響應速度就已經優化成幾十毫秒了,是不是很開心?從幾秒變成了幾十毫秒!

第二步

那個超時的時間,也就是上面那段ribbon和hystrix的超時時間設置。

奉勸各位同學,不要因為系統接口的性能過差而懶惰,搞成幾秒甚至幾十秒的超時,一般超時定義在1秒以內,是比較通用以及合理的。

為什麽這麽說?

因為一個接口,理論的最佳響應速度應該在200ms以內,或者慢點的接口就幾百毫秒。

如果一個接口響應時間達到1秒+,建議考慮用緩存、索引、NoSQL等各種你能想到的技術手段,優化一下性能。

否則你要是胡亂設置超時時間是幾秒,甚至幾十秒,萬一下遊服務偶然出了點問題響應時間長了點呢?那你這個線程池裏的線程立馬全部卡死!

具體hystrix的線程池以及超時時間的最佳生產實踐,請見下一篇文章:《微服務架構如何保障雙11狂歡下的99.99%高可用》

這兩步解決之後,其實系統表現就正常了,核心服務B響應速度很快,而且超時時間也在1秒以內,不會出現hystrix線程池頻繁卡死的情況了

第三步

事兒還沒完,你要真覺得兩步就搞定了,那還是經驗不足。

如果你要是超時時間設置成了1秒,如果就是因為偶然發生的網絡抖動,導致接口某次調用就是在1.5秒呢?這個是經常發生的,因為網絡的問題,接口調用偶然超時。

所以此時配合著超時時間,一般都會設置一個合理的重試,如下所示:

技術分享圖片

設置這段重試之後,Spring Cloud中的Feign + Ribbon的組合,在進行服務調用的時候,如果發現某臺機器超時請求失敗,會自動重試這臺機器,如果還是不行會換另外一臺機器重試。

這樣由於偶爾的網絡請求造成的超時,不也可以通過自動重試避免了?

第四步

其實事兒還沒完,如果把重試參數配置了,結果你居然就放手了,那還是沒對人家負責任啊!

你的系統架構中,只要涉及到了重試,那麽必須上接口的冪等性保障機制

否則的話,試想一下,你要是對一個接口重試了好幾次,結果人家重復插入了多條數據,該怎麽辦呢?

其實冪等性保證本身並不復雜,根據業務來,常見的方案:

  • 可以在數據庫裏建一個唯一索引,插入數據的時候如果唯一索引沖突了就不會插入重復數據

  • 或者是通過redis裏放一個唯一id值,然後每次要插入數據,都通過redis判斷一下,那個值如果已經存在了,那麽就不要插入重復數據了。

類似這樣的方案還有一些。總之,保證一個接口被多次調用的時候,不能插入重復的數據。

六、總結全文,回眸再看

有圖有真相!老規矩,最後給大家上一張圖,最終優化後的系統表現大概是長下面這樣子的。

技術分享圖片

轉自:https://mp.weixin.qq.com/s/aH0LHgfhxpvp1IY-XbEMWA

每秒上萬並發下的Spring Cloud參數優化實戰