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