高併發服務的幾條優化經驗

danny_2018發表於2022-05-17

前言:如何優化高併發服務,這裡指的是qps在20萬以上的線上服務,注意不是離線服務,線上服務會存在哪些挑戰呢?①無法做離線快取,所有的資料都是實時讀的 大量的請求會打到線上服務,對於服務的響應時間要求較高,一般都是限制要求在300ms以內,如果超過這個時間那麼對使用者造成的體驗就會急劇下降資料量較大,單次如果超過50W的qps,單條1kb,50萬就是5GB了,1分鐘30G,對於底層的資料儲存與訪問都有巨大的壓力~ 如何應對這些棘手的問題,本篇部落格來討論一下。

一:向關係型資料庫sayno

一個真正的大型網際網路面向c端的服務都不會直接使用資料庫作為自己的儲存系統,無論你是採用的是分庫分表還是底層用了各種優秀的連線池等,mysql/oracle在面對大型線上服務是存在天然的劣勢,再如何優化,也難以抵擋qps大於50萬流量帶來的衝擊。所以換個思路,我們必須使用nosql類快取系統,比如redis/mermCache等作為自己的"資料庫",而mysql等關係型資料庫只是一種兜底,用於非同步去寫作為資料查詢的備份系統。

場景舉例:京東雙11主會場,上架了部分商品,這部分商品都是在會場開始上架的時候直接寫入redis中的,當上架完成之後,通過非同步訊息寫入到mysql中。面向c端的查詢都是直接讀redis,而不是資料庫.而b端的查詢,可以走資料庫去查詢。這部分流量不是很高,資料庫絕對可以抵擋的住。

二:多級快取

都知道快取是高併發提高效能的利器之一。而如何使用好快取進而利用好多級快取,是需要我們去思考的問題。redis目前是快取的第一首選.單機可達6-8萬的qps,在面對高併發的情況下,我們可以手動的水平擴容,以達到應對qps可能無線增長的場景。但是這種做法也存在弊端,因為redis是單執行緒的,並且會存在熱點問題。雖然redis內部用crc16演算法做了hash打散,但是同一個key還是會落到一個單獨的機器上,就會使機器的負載增加,redis典型的存在快取擊穿和快取穿透兩個問題,尤其在秒殺這個場景中,如果要解決熱點問題,就變的比較棘手。這個時候多級快取就必須要考慮了,典型的在秒殺的場景中,單sku商品在售賣開始的瞬間,qps會急劇上升.而我們這時候需要用memeryCache來擋一層,memeryCache是多執行緒的,比redis擁有更好的併發能力,並且它是天然可以解決熱點問題的。有了memeryCache,我們還需要localCache,本地快取,這是一種以記憶體換速度的方式。本地快取會接入使用者的第一層請求,如果它找不到,接下來走memeryCache,然後走redis,這套流程下來可以擋住百萬的qps.

三:多執行緒

我記得在剛開始入行的時候,每次面試都會被問到多執行緒,那時候是一臉懵逼,多執行緒有這麼厲害嗎?幹嘛都說多執行緒,為什麼要使用多執行緒,不用行不行?要講明這個道理,我先來說一個例項.曾經我優化過一個介面,很典型的一個場景。原始的方式是迴圈一個30-40萬的list,list執行的操作很簡單,就是讀redis的資料,讀一次大概需要3ms左右,這是同步的方式,在預覽環境測試,直接30秒+超時。後來優化的方式就是把原有的同步呼叫改為執行緒池呼叫,執行緒池裡的執行緒數或阻塞佇列大小需要自己調優,最後實測介面rt只需要3秒。足以見多執行緒的威力。在多核服務的今天,如果還不用多執行緒就是對伺服器資源的一種浪費。這裡需要說一句,使用多線層一定要做好監控,你需要隨時知道執行緒的狀態,如果執行緒數和queueSize設定的不恰當,將會嚴重影響業務~ 當然多執行緒也要分場景,如果為了多執行緒而多執行緒反而是一種浪費,因為多執行緒排程的時候會造成執行緒在核心態和使用者態之間來回切換,如果使用不當反而會有反作用

四: 降級和熔斷

降級和熔斷是一種自我保護措施,這和電路上的熔斷器的基本原理是一樣的,防止電流過大引起火災等,面對不可控的巨大流量請求很有可能會擊垮伺服器的資料庫或者redis,使伺服器當機或者癱瘓造成不可挽回的損失。因為我們服務的本身需要有防禦機制,以抵擋外部服務對於自身的侵入導致服務受損引起連帶反應。降級和熔斷有所不同,兩者的區別在於降級是將一些線上主鏈路的功能關閉,不影響到主鏈路.熔斷的話,是指A請求B,B檢測到服務流量多大啟動了熔斷,那麼請求會直接進入熔斷池,直接返回失敗。如何抉擇使用哪一個需要在實際中結合業務場景來考慮.

五: 優化IO

很多人都會忽視IO這個問題,頻繁的建聯和斷聯都是對系統的重負。在併發請求中,如果存在單個請求的放大效那麼將會使io呈指數倍增加。舉個例子,比如主會場的商品資訊,如果需要商品的某個具體的詳情,而這個詳情需要呼叫下游來單個獲取.隨著主會場商品的熱賣,商品越來越多,一次就要經過商品數X下游請求的數量,在海量的qps請求下,IO數被佔據,大量的請求被阻塞,介面的響應速度就會呈指數級下降。所以需要批量的請求介面,所有的優化為一次IO

六: 慎用重試

重試作為對臨時異常的一種處理的常見手法,常見應對的方式是請求某個服務失敗或者寫資料庫了重新再試,使用重試一定要注意以下幾點①控制好重試次數②重試的間隔時間得衡量好③是否重試要做到配置化。之前我們線上出了一個bug,kafka消費出現了嚴重的lag,單詞消耗時間是10幾秒,看程式碼之後發現是重試的次數過多導致的,並且次數還不支援配置化修改,所以當時的做法只能是臨時改程式碼後上線.重試作為一種業務的二次嘗試,極大提升了程式的請求success,但是也要注意以上幾點。

七:邊界case的判斷和兜底

作為網際網路老手,很多人寫出的程式碼都不錯,但是在經歷過幾輪的故障review之後發現很多釀成重大事故的程式碼背後都是缺少對一些邊界問題的處理,所犯的錯誤非常簡單,但是往往就是這些小問題就能釀成大事故.曾經review過一次重大的事故,後來發現最終的原因居然是沒有對空陣列進行判空,導致傳入下游的rpc是空的,下游直接返回全量的業務資料,影響數百萬使用者。這個程式碼改動起來很簡單,但是是令人需要反省的,小小的不足釀成了大禍

八:學會優雅的列印日誌

日誌作為追溯線上問題的最佳利器,可謂保留bug現場的唯一來源。雖然有arthas這樣的利器方便我們排查問題,但是對於一些比較複雜的場景,還是需要日誌來記錄程式的資料.但是在高流量的場景中,如果全量列印日誌對於線上來說就是一種災難,有以下缺點①嚴重佔用磁碟,估算以下,如果介面的qps在20萬左右,日誌一秒就幾千兆,一天下來就是上千GB ②大量的日誌需要輸出,佔用了程式IO,增加了介面的RT(響應時間) 如果需要解決這個問題,①我們可以利用限流元件來實現一個基於限流的日誌元件,令牌桶演算法可以限制列印日誌的流量,比如一秒只允許列印一條日誌 ②基於白名單的日誌列印,線上配置了白名單使用者才可以列印出來,節省了大量了無效日誌輸出

總結: 本篇部落格討論了高併發服務在面對大流量時的一些基本注意事項和應對的點,當然實際線上的比目前的更復雜,這裡只是給出幾條建議,希望我們在高併發的路上保持敬畏,繼續探索.更好的深耕c端服務,做更好的網際網路應用。

來自 “ 架構文摘 ”, 原文作者:Yrion;原文連結:www.cnblogs.com/wyq178/p/15811956.html,如有侵權,請聯絡管理員刪除。

相關文章