用我的事故告訴你:掌握異步很關鍵
摘要:在高並發的場景下,異步是一個極其重要的優化方向。
本文分享自華為雲社區《一次線上事故,我頓悟了異步的精髓》,作者:勇哥java實戰分享。
在高並發的場景下,異步是一個極其重要的優化方向。
前段時間,生產環境發生一次事故,筆者認為事故的場景非常具備典型性 。寫這篇文章,筆者想和大家深入探討該場景的架構優化方案。希望大家讀完之後,可以對異步有更深刻的理解。
1 業務場景
老師登錄教研平台,會看到課程列表,點擊課程後,課程會以視頻的形式展現出來。
訪問課程詳情頁面,包含兩個核心動作:
1.讀取課程視頻信息 :
從緩存服務器 Redis 獲取課程的視頻信息 ,返回給前端,前端通過視頻組件渲染。
2.寫入課程觀看行為記錄 :
當教師觀看視頻的過程中,瀏覽器每隔3秒發起請求,教研服務將觀看行為記錄插入到數據庫表中。而且隨着用戶在線人數越多,寫操作的頻率也會指數級增長。
上線初期,這種設計運行還算良好,但隨着在線用戶的增多,系統響應越來越慢,大量線程阻塞在寫入視頻觀看進度表上。
首先我們會想到一個非常直觀的方案,提升寫入數據庫的能力。
- 優化 SQL 語句;
- 提升 MySQL 數據庫硬件配置 ;
- 分庫分表。
這種方案其實也可以滿足我們的需求,但是通過擴容硬件並不便宜,另外寫操作可以允許適當延遲和丟失少量數據,那這種方案更顯得性價比不足。
那麼架構優化的方嚮應該是:「減少寫動作的耗時,提升寫動作的並發度」, 只有這樣才能讓系統更順暢的運行。
於是,我們想到了第二種方案:寫請求異步化。
- 線程池模式
- 本地內存 + 定時任務
- MQ 模式
- Agent 服務 + MQ 模式
2 線程池模式
2014年,筆者在藝龍旅行網負責紅包系統相關工作。運營系統會調用紅包系統給特定用戶發送紅包,當這些用戶登錄 app 後,app 端會調用紅包系統的激活紅包接口 。
激活紅包接口是一個寫操作,速度也比較快(20毫秒左右),接口的日請求量在2000萬左右。
應用訪問高峰期,紅包系統會變得不穩定,激活接口經常超時,筆者為了快速解決問題,採取了一個非常粗糙的方案:
“控制器收到請求後,將寫操作放入到獨立的線程池中後,立即返回給前端,而線程池會異步執行激活紅包方法”。
坦率的講,這是一個非常有效的方案,優化後,紅包系統非常穩定。
回到教研的場景,見下圖,我們也可以設計類似線程池模型的方案:
使用線程池模式,需要注意如下幾點:
- 線程數不宜過高,避免佔用過多的數據庫連接池 ;
- 需要考慮評估線程池隊列的大小,以免出現內存溢出的問題。
3 本地內存 + 定時任務
開源中國統計瀏覽數的方案非常經典。
用戶訪問過一次文章、新聞、代碼詳情頁面,訪問次數字段加 1 , 在 oschina 上這個操作是異步的,訪問的時候只是將數據在內存中保存,每隔固定時間將這些數據寫入數據庫。
示例代碼如下:
我們可以借鑒開源中國的方案 :
- 控制器接收請求後,觀看進度信息存儲到本地內存 LinkedBlockingQueue 對象里;
- 異步線程每隔1分鐘從隊列里獲取數據 ,組裝成 List 對象,最後調用 Jdbc batchUpdate 方法批量寫入數據庫;
- 批量寫入主要是為了提升系統的整體吞吐量,每次批量寫入的 List 大小也不宜過大 。
這種方案優點是:不改動原有業務架構,簡單易用,性能也高。該方案同樣需要考慮內存溢出的風險。
4 MQ 模式
很多同學們會想到 MQ 模式 ,消息隊列最核心的功能是異步和解耦,MQ 模式架構清晰,易於擴展。
核心流程如下:
- 控制器接收寫請求,將觀看視頻行為記錄轉換成消息 ;
- 教研服務發送消息到 MQ ,將寫操作成功信息返回給前端 ;
- 消費者服務從 MQ 中獲取消息 ,批量操作數據庫 。
這種方案優點是:
- MQ 本身支持高可用和異步,發送消息效率高 , 也支持批量消費;
- 消息在 MQ 服務端會持久化,可靠性要比保存在本地內存高;
不過 MQ 模式需要引入新的組件,增加額外的複雜度。5 Agent 服務 + MQ 模式
互聯網大廠還有一種常見的異步的方案:Agent 服務 + MQ 模式。
教研服務器上部署 Agent 服務(獨立的進程) , 教研服務接收寫請求後,將請求按照固定的格式(比如 JSON )寫入到本次磁盤中,然後給前端返回成功信息。
Agent 服務會監聽文件變動,將文件內容發送到消息隊列 , 消費者服務獲取觀看行為記錄,將其存儲到 MySQL 數據庫中。
還有一種演進,假設我們不想在應用中依賴消息隊列,不生成本地文件,可以採用如下的方式:
這種方案最大的優點是:架構分層清晰,業務服務不需要引入 MQ 組件。
筆者原來接觸過的性能監控平台,或者日誌分析平台都使用這種模式。
5 總結
學習需要一層一層遞進的思考。
第一層:什麼場景下需要異步
- 大量寫操作佔用了過多的資源,影響了系統的正常運行;
- 寫操作異步後,不影響主流程,允許適當延遲;
第二層:異步的外功心法
本文提到了四種異步方式:
- 線程池模式
- 本地內存 + 定時任務
- MQ 模式
- Agent 服務 + MQ 模式
它們的共同特點是:將寫操作命令存儲在一個池子後,立刻響應給前端,減少寫動作的耗時。任務服務異步從池子里獲取任務後執行。
第三層:異步的本質
在筆者看來,異步是更細粒度的使用系統資源的一種方式。
在教研課程詳情場景里,數據庫的資源是固定的,但寫操作佔據大量數據庫資源,導致整個系統的阻塞,但寫操作並不是最核心的業務流程,它不應該佔用那麼多的系統資源。
我們使用異步的解決方案時,無論是使用線程池,還是本地內存 + 定時任務 ,亦或是 MQ ,對數據庫資源的使用都需要在合理的範圍內,只有這樣系統才能順暢的運行。