藝龍紅包領(lǐng)取接口頻繁超時(shí),一次典型的線程池異步技巧實(shí)戰(zhàn)
2014 年是互聯(lián)網(wǎng)紅包大戰(zhàn)元年,筆者加入藝龍旅行網(wǎng),負(fù)責(zé)的第一個(gè)重要系統(tǒng)就是:紅包系統(tǒng)。
這篇文章,筆者分享藝龍紅包領(lǐng)取接口頻繁超時(shí),如何巧用線程池異步解決超時(shí)問(wèn)題 。
圖片
一、系統(tǒng)架構(gòu) & 接口事故
圖片
如圖,用戶登錄藝龍 APP 后,藝龍 APP 會(huì)自動(dòng)調(diào)用領(lǐng)取紅包的接口 。
紅包服務(wù)會(huì)查詢?cè)撚脩羰欠駶M足領(lǐng)取紅包的條件,若滿足條件,則發(fā)送消息到 RabbitMQ , 發(fā)送成功后,領(lǐng)取接口返回響應(yīng)值給前端。
用戶服務(wù)作為消費(fèi)者,異步消費(fèi) MQ 的消息,將紅包虛擬金額保存到用戶余額體系里。
從整體流程來(lái)看,還是非常簡(jiǎn)單的,偽代碼如下:
圖片
但筆者剛加入團(tuán)隊(duì)時(shí),APP 研發(fā)團(tuán)隊(duì)的總監(jiān)經(jīng)常投訴我們,因?yàn)槲覀兊念I(lǐng)取接口每隔一段時(shí)間后就會(huì)超時(shí),重啟紅包活動(dòng)后,接口才恢復(fù)正常。
首先查看服務(wù)器日志,發(fā)現(xiàn)日志異常集中于第三步,即:發(fā)送消息到 RabbitMQ 。
為什么發(fā)送消息到 RabbitMQ 會(huì)失敗呢 ?
筆者首先想到的是:Linux netstat 命令查看 RabbitMQ 連接狀況。
二、Linux netstat 命令查看 RabbitMQ 連接
netstat 可以查看服務(wù)器當(dāng)前端口列表及指定端口的連接狀態(tài)等,參數(shù)如下:
圖片
常用命令:
1 、查看當(dāng)前所有 tcp 端口
圖片
2 、查看當(dāng)前所有 udp 端口
圖片
3 、顯示系統(tǒng)所有端口
圖片
假如想查看某個(gè)應(yīng)用對(duì)應(yīng)的連接,可以通過(guò) grep pid 來(lái)實(shí)現(xiàn),如下圖:
圖片
當(dāng)筆者在生產(chǎn)環(huán)境使用 netstat 命令查看紅包服務(wù)的連接,發(fā)現(xiàn) RabbitMQ 連接的特別多 ,基本達(dá)到了本地的最大句柄數(shù)。
于是,筆者開(kāi)始懷疑 RabbitMQ SDK 封裝有問(wèn)題,我們來(lái)一探究竟。
三、RabbitMQ SDK 封裝問(wèn)題
圖片
如上圖,RabbitMQ 工具類發(fā)送消息時(shí),首先看本地緩存 longConnection 是否有效,若有效,則直接復(fù)用該連接,若連接失效,則重新創(chuàng)建連接,然后通過(guò)該連接發(fā)送消息。
看到這里,似乎也沒(méi)有問(wèn)題呀 ?
但高并發(fā)場(chǎng)景下,請(qǐng)求量非常高,確有隱藏的風(fēng)險(xiǎn),表現(xiàn)在如下兩點(diǎn):
1、獲取連接時(shí),通過(guò)對(duì)象的屬性 longConnection 來(lái)判斷是否過(guò)期 , 但我們知道對(duì)于多線程來(lái)講,在沒(méi)有加鎖的情況下,并不靠譜。
圖片
2、創(chuàng)建連接時(shí),極大可能存在并發(fā)問(wèn)題,導(dǎo)致會(huì)重復(fù)創(chuàng)建多個(gè)連接,而且重復(fù)的連接并沒(méi)有引用。
綜上,使用架構(gòu)封裝的 RabbitMQ. SDK 在發(fā)送消息時(shí),容易產(chǎn)生連接泄露的問(wèn)題。
當(dāng)連接數(shù)占滿之后,再次發(fā)送消息時(shí),由于長(zhǎng)時(shí)間獲取不到連接,拋出異常。
那么如何快速解決呢 ?
首先,我們想到的是:讓架構(gòu)團(tuán)隊(duì)快速修復(fù) SDK 的 BUG ,但是這個(gè)需求時(shí)間(研發(fā)和測(cè)試),于是筆者想到的是線程池異步技巧來(lái)解決這個(gè)問(wèn)題。
四、線程池異步技巧
當(dāng) APP 發(fā)起領(lǐng)取紅包接口后,紅包服務(wù)開(kāi)啟單獨(dú)的線程處理該請(qǐng)求,然后直接將響應(yīng)結(jié)果直接返回給前端。
圖片
偽代碼如下:
圖片
首先,我們定義了一個(gè)單線程,領(lǐng)取接口接收到請(qǐng)求后,調(diào)用線程池的 execute 方法,直接將響應(yīng)結(jié)果返回給前端,線程池會(huì)異步的處理領(lǐng)取紅包流程 。
這么做有兩點(diǎn)好處:
1、將領(lǐng)取的流程異步化,可以減少領(lǐng)取接口的阻塞,讓 Tomcat 線程可以非常順暢的運(yùn)行 ;
2、因?yàn)槭菃尉€程處理領(lǐng)取流程,可以規(guī)避 RabbitMQ SDK 連接泄露的 BUG ,同時(shí)也可以滿足業(yè)務(wù)需求。
五、總結(jié)
藝龍紅包 RabbitMQ SDK 的 連接泄漏 BUG 非常隱蔽,筆者通過(guò) netstat 命令定位到紅包服務(wù)的 RabbitMQ 連接發(fā)生了泄露 。
架構(gòu)團(tuán)隊(duì)封裝的 RabbitMQ SDK 有兩種方案來(lái)解決:
1、創(chuàng)建連接時(shí)完善加鎖的操作 ;
2、使用 commons-pool 這樣的框架來(lái)創(chuàng)建連接池,提高可維護(hù)性。
最后,因?yàn)闀r(shí)間的關(guān)系,筆者要快速解決問(wèn)題,采用了異步線程池的模式 ,單線程處理領(lǐng)取流程,可以規(guī)避 RabbitMQ SDK 連接泄露的 BUG 。