溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

發(fā)布時(shí)間:2021-10-20 09:39:53 來源:億速云 閱讀:868 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故”吧!

摘要

元旦期間 訂單業(yè)務(wù)線 告知 推送系統(tǒng) 無法正常收發(fā)消息,作為推送系統(tǒng)維護(hù)者的我正外面瀟灑,無法第一時(shí)間回去,直接讓 ops 幫忙重啟服務(wù),一切好了起來,重啟果然是個(gè)大殺器。由于推送系統(tǒng)本身是分布式部署,消息有做各種的可靠性策略,所以重啟是不會(huì)丟失消息事件的。

事后通過日志分析有大量的 redis 的報(bào)錯(cuò),十分鐘內(nèi)有 16w 次的錯(cuò)誤。日志的錯(cuò)誤是 connect: cannot assign requested address 。該錯(cuò)誤不是推送服務(wù)內(nèi)部及 redis 庫返回的 error,而是系統(tǒng)回饋的 errno 錯(cuò)誤。

這個(gè)錯(cuò)誤是由于無法申請(qǐng)可用地址引起的,也就是無法申請(qǐng)到可用的 socket。

話說,元旦當(dāng)天在線數(shù)和訂單量確實(shí)大了不少,往常推送系統(tǒng)的長連接客戶端在 35w,這次峰值飆到 50w 左右, 集群共 6 個(gè)節(jié)點(diǎn),其中有 4 個(gè)節(jié)點(diǎn)每個(gè)都抗了 9w+ 的長連接。另外,推送的消息量也隨之翻倍。

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

分析

下面是 kibana 日志的統(tǒng)計(jì),出錯(cuò)的時(shí)間區(qū)間里有近 16w 次的 redis 報(bào)錯(cuò)。

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

下面是出問題節(jié)點(diǎn)的 TCP 連接狀況,可以看到 established 在 6w,而 time-wait 連接干到 2w 多個(gè)。

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

為什么會(huì)產(chǎn)生這么多 time-wait?誰主動(dòng)關(guān)閉就就有 time-wait,但推送系統(tǒng)除了協(xié)議解析失敗之外,其余情況都不會(huì)主動(dòng) close 客戶端,哪怕是鑒權(quán)失敗和弱網(wǎng)絡(luò)客戶端寫緩沖爆滿,事后通過日志也確定了不是推送系統(tǒng)自身產(chǎn)生的 tw。

另外,linux 主機(jī)被 ops 交付時(shí)應(yīng)該有做內(nèi)核調(diào)優(yōu)初始化的,在開啟 tw_reuse 參數(shù)后,time-wait 是可以復(fù)用的。難道是沒開啟 reuse?

查看 sysctl.conf 的內(nèi)核參數(shù)得知,果然 tcp_tw_reuse 參數(shù)沒有打開,不能快速地復(fù)用還處在 time-wait 狀態(tài)的地址,只能等待 time-wait 的超時(shí)關(guān)閉,rfc 協(xié)議里規(guī)定等待 2 分鐘左右,開啟 tw_reuse可在 1s 后復(fù)用該地址。另外 ip_local_port_range 端口范圍也不大,縮短了可用的連接范圍。

sysctl  -a|egrep "tw_reuse|timestamp|local_port"  net.ipv4.ip_local_port_range = 35768    60999 net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_tw_reuse = 0

所以,由于沒有可用地址才爆出了 connect: cannot assign requested address 錯(cuò)誤。

內(nèi)在問題

追究問題

上面是表象問題,來查查為什么會(huì)有這么多的 time-wait ?再說一遍,通常哪一端主動(dòng) close fd,哪一端就會(huì)產(chǎn)生 time-wait。事后通過 netstat 得知 time-wait 連接基本是來自 redis 主機(jī)。

下面是推送代碼中的連接池配置,空閑連接池只有 50,最大可以 new 的連接可以到 500 個(gè)。這代表當(dāng)有大量請(qǐng)求時(shí),企圖先從 size 為 50 的連接池里獲取連接,如果拿不到連接則 new 一個(gè)新連接,連接用完了后需要?dú)w還連接池,如果這時(shí)候連接池已經(jīng)滿了,那么該連接會(huì)主動(dòng)進(jìn)行 close 關(guān)閉。

MaxIdle   = 50 MaxActive = 500 Wait      = false

除此之外,還發(fā)現(xiàn)一個(gè)問題。有幾處 redis 的處理邏輯是異步的,比如每次收到心跳包都會(huì) go 一個(gè)協(xié)程去更新 redis, 這也加劇了連接池的搶奪,改為同步代碼。這樣在一個(gè)連接上下文中同時(shí)只對(duì)一個(gè) redis 連接操作。

解決方法

調(diào)大 golang redis client 的 maxIdle 連接池大小,避免了高下無空閑連接而新建連接和池子爆滿又不能歸還連接的尷尬場(chǎng)面。當(dāng) pool wait 為 true 時(shí),意味著如果空閑池中沒有可用的連接,且當(dāng)前已建立連接的連接數(shù)大于 MaxActive 最大空閑數(shù),則一直阻塞等待其他人歸還連接。反之直接返回 “connection pool exhausted” 錯(cuò)誤。

MaxIdle   = 300 MaxActive = 400 Wait      = true

redis 的 qps 性能瓶頸

redis 的性能一直是大家所稱贊的,在不使用 redis 6.0 multi io thread 下,QPS 一般可以在 13w 左右,如果使用多指令和 pipeline 的話,可以干到 40w 的 OPS 命令數(shù),當(dāng)然 qps 還是在 12w-13w 左右。

Redis QPS 高低跟 redis 版本和 cpu hz、cache 存在正比關(guān)系

根據(jù)我的經(jīng)驗(yàn),在內(nèi)網(wǎng)環(huán)境下且已實(shí)例化連接對(duì)象,單條 redis 指令請(qǐng)求耗時(shí)通常在 0.2ms 左右,200us 已經(jīng)夠快了,但為什么還會(huì)有大量因 redis client 連接池?zé)o空閑連接而建立新連接的情況?

通過 grafana 監(jiān)控分析 redis 集群,發(fā)現(xiàn)有幾個(gè)節(jié)點(diǎn) QPS 已經(jīng)到了 Redis 單實(shí)例性能瓶頸,QPS 干到了近 15w 左右。難怪不能快速處理來自業(yè)務(wù)的 redis 請(qǐng)求。這個(gè)瓶頸必然會(huì)影響請(qǐng)求的時(shí)延。請(qǐng)求的時(shí)延都高了,連接池不能及時(shí)返回連接池,所以就造成了文章開頭說的問題??傊瑯I(yè)務(wù)流量的暴增引起了一系列問題。

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

發(fā)現(xiàn)問題,那么就要解決問題,redis 的 qps 優(yōu)化方案有兩步:

  • 擴(kuò)容 redis 節(jié)點(diǎn),遷移 slot 使其分擔(dān)流量

  • 盡量把程序中 redis 的請(qǐng)求改成批量模式

增加節(jié)點(diǎn)容易,批量也容易。起初在優(yōu)化推送系統(tǒng)時(shí),已經(jīng)把同一個(gè)邏輯中的 redis 操作改為批量模式了。但問題來了,很多的 redis 操作在不同的邏輯塊里面,沒法合成一個(gè) pipeline。

然后做了進(jìn)一步的優(yōu)化,把不同邏輯中的 redis 請(qǐng)求合并到一個(gè) pipeline 里,優(yōu)點(diǎn)在于提高了 redis 的吞吐,減少了 socket 系統(tǒng)調(diào)用、網(wǎng)絡(luò)中斷開銷,缺點(diǎn)是增加了邏輯復(fù)雜度,使用 channal 管道做隊(duì)列及通知增加了 runtime 調(diào)度開銷,pipeline worker 觸發(fā)條件是滿足 3 個(gè) command 或 5ms 超時(shí),定時(shí)器采用分段的時(shí)間輪。

對(duì)比優(yōu)化修改前,cpu開銷減少了 3% 左右,壓測(cè)下redis qps平均降了 3w 左右差值,最多可以降到 7w 左右,當(dāng)然概率上消息的時(shí)延會(huì)高了幾個(gè)ms。

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

實(shí)現(xiàn)的邏輯參考下圖,調(diào)用方把redis command和接收結(jié)果的chan推送到任務(wù)隊(duì)列中,然后由一個(gè)worker去消費(fèi),worker組裝多個(gè)redis cmd為pipeline,向redis發(fā)起請(qǐng)求并拿回結(jié)果,拆解結(jié)果集后,給每個(gè)命令對(duì)應(yīng)的結(jié)果chan推送結(jié)果。調(diào)用方在推送任務(wù)到隊(duì)列后,就一直監(jiān)聽傳輸結(jié)果的chan。

如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故

感謝各位的閱讀,以上就是“如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)如何解決高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI