高并發(fā)服務(wù)的幾條優(yōu)化經(jīng)驗
前言:如何優(yōu)化高并發(fā)服務(wù),這里指的是qps在20萬以上的在線服務(wù),注意不是離線服務(wù),在線服務(wù)會存在哪些挑戰(zhàn)呢?①無法做離線緩存,所有的數(shù)據(jù)都是實時讀的 ②大量的請求會打到線上服務(wù),對于服務(wù)的響應(yīng)時間要求較高,一般都是限制要求在300ms以內(nèi),如果超過這個時間那么對用戶造成的體驗就會急劇下降 ③數(shù)據(jù)量較大,單次如果超過50W的qps,單條1kb,50萬就是5GB了,1分鐘30G,對于底層的數(shù)據(jù)存儲與訪問都有巨大的壓力~ 如何應(yīng)對這些棘手的問題,本篇博客來討論一下
一:向關(guān)系型數(shù)據(jù)庫sayno
一個真正的大型互聯(lián)網(wǎng)面向c端的服務(wù)都不會直接使用數(shù)據(jù)庫作為自己的存儲系統(tǒng),無論你是采用的是分庫分表還是底層用了各種優(yōu)秀的連接池等,mysql/oracle在面對大型在線服務(wù)是存在天然的劣勢,再如何優(yōu)化,也難以抵擋qps大于50萬流量帶來的沖擊。所以換個思路,我們必須使用nosql類緩存系統(tǒng),比如redis/mermCache等作為自己的"數(shù)據(jù)庫",而mysql等關(guān)系型數(shù)據(jù)庫只是一種兜底,用于異步去寫作為數(shù)據(jù)查詢的備份系統(tǒng)。
場景舉例:京東雙11主會場,上架了部分商品,這部分商品都是在會場開始上架的時候直接寫入redis中的,當(dāng)上架完成之后,通過異步消息寫入到mysql中。面向c端的查詢都是直接讀redis,而不是數(shù)據(jù)庫.而b端的查詢,可以走數(shù)據(jù)庫去查詢。這部分流量不是很高,數(shù)據(jù)庫絕對可以抵擋的住。
二:多級緩存
都知道緩存是高并發(fā)提高性能的利器之一。而如何使用好緩存進而利用好多級緩存,是需要我們?nèi)ニ伎嫉膯栴}。redis目前是緩存的第一首選.單機可達6-8萬的qps,在面對高并發(fā)的情況下,我們可以手動的水平擴容,以達到應(yīng)對qps可能無線增長的場景。但是這種做法也存在弊端,因為redis是單線程的,并且會存在熱點問題。雖然redis內(nèi)部用crc16算法做了hash打散,但是同一個key還是會落到一個單獨的機器上,就會使機器的負(fù)載增加,redis典型的存在緩存擊穿和緩存穿透兩個問題,尤其在秒殺這個場景中,如果要解決熱點問題,就變的比較棘手。這個時候多級緩存就必須要考慮了,典型的在秒殺的場景中,單sku商品在售賣開始的瞬間,qps會急劇上升.而我們這時候需要用memeryCache來擋一層,memeryCache是多線程的,比redis擁有更好的并發(fā)能力,并且它是天然可以解決熱點問題的。有了memeryCache,我們還需要localCache,本地緩存,這是一種以內(nèi)存換速度的方式。本地緩存會接入用戶的第一層請求,如果它找不到,接下來走memeryCache,然后走redis,這套流程下來可以擋住百萬的qps.
三:多線程
四: 降級和熔斷
降級和熔斷是一種自我保護措施,這和電路上的熔斷器的基本原理是一樣的,防止電流過大引起火災(zāi)等,面對不可控的巨大流量請求很有可能會擊垮服務(wù)器的數(shù)據(jù)庫或者redis,使服務(wù)器宕機或者癱瘓造成不可挽回的損失。因為我們服務(wù)的本身需要有防御機制,以抵擋外部服務(wù)對于自身的侵入導(dǎo)致服務(wù)受損引起連帶反應(yīng)。降級和熔斷有所不同,兩者的區(qū)別在于降級是將一些線上主鏈路的功能關(guān)閉,不影響到主鏈路.熔斷的話,是指A請求B,B檢測到服務(wù)流量多大啟動了熔斷,那么請求會直接進入熔斷池,直接返回失敗。如何抉擇使用哪一個需要在實際中結(jié)合業(yè)務(wù)場景來考慮.
五: 優(yōu)化IO
很多人都會忽視IO這個問題,頻繁的建聯(lián)和斷聯(lián)都是對系統(tǒng)的重負(fù)。在并發(fā)請求中,如果存在單個請求的放大效那么將會使io呈指數(shù)倍增加。舉個例子,比如主會場的商品信息,如果需要商品的某個具體的詳情,而這個詳情需要調(diào)用下游來單個獲取.隨著主會場商品的熱賣,商品越來越多,一次就要經(jīng)過商品數(shù)X下游請求的數(shù)量,在海量的qps請求下,IO數(shù)被占據(jù),大量的請求被阻塞,接口的響應(yīng)速度就會呈指數(shù)級下降。所以需要批量的請求接口,所有的優(yōu)化為一次IO
六: 慎用重試
重試作為對臨時異常的一種處理的常見手法,常見應(yīng)對的方式是請求某個服務(wù)失敗或者寫數(shù)據(jù)庫了重新再試,使用重試一定要注意以下幾點①控制好重試次數(shù)②重試的間隔時間得衡量好③是否重試要做到配置化。之前我們線上出了一個bug,kafka消費出現(xiàn)了嚴(yán)重的lag,單詞消耗時間是10幾秒,看代碼之后發(fā)現(xiàn)是重試的次數(shù)過多導(dǎo)致的,并且次數(shù)還不支持配置化修改,所以當(dāng)時的做法只能是臨時改代碼后上線.重試作為一種業(yè)務(wù)的二次嘗試,極大提升了程序的請求success,但是也要注意以上幾點。
七:邊界case的判斷和兜底
作為互聯(lián)網(wǎng)老手,很多人寫出的代碼都不錯,但是在經(jīng)歷過幾輪的故障review之后發(fā)現(xiàn)很多釀成重大事故的代碼背后都是缺少對一些邊界問題的處理,所犯的錯誤非常簡單,但是往往就是這些小問題就能釀成大事故.曾經(jīng)review過一次重大的事故,后來發(fā)現(xiàn)最終的原因居然是沒有對空數(shù)組進行判空,導(dǎo)致傳入下游的rpc是空的,下游直接返回全量的業(yè)務(wù)數(shù)據(jù),影響數(shù)百萬用戶。這個代碼改動起來很簡單,但是是令人需要反省的,小小的不足釀成了大禍
八:學(xué)會優(yōu)雅的打印日志
總結(jié): 本篇博客討論了高并發(fā)服務(wù)在面對大流量時的一些基本注意事項和應(yīng)對的點,當(dāng)然實際線上的比目前的更復(fù)雜,這里只是給出幾條建議,希望我們在高并發(fā)的路上保持敬畏,繼續(xù)探索.更好的深耕c端服務(wù),做更好的互聯(lián)網(wǎng)應(yīng)用,加油
熱門推薦:
PS:如果覺得我的分享不錯,歡迎大家隨手點贊、轉(zhuǎn)發(fā)、在看。

