有贊使用storm已經有将近3年(nián)時間,穩定支撐着實時統計、數據同步、對賬、監控、風控等業務。訂單實時統計是其中一(yī)個典型的(de)業務,對數據準确性、性能等方面都有較高(gāo)要求,也是上線時間最久的(de)一(yī)個實時計算應用。通過訂單實時統計,描述使用storm時,遇到的(de)準确性、性能、可(kě)靠性等方面的(de)問題。
訂單實時統計的(de)演進
第一(yī)版:流程走通
在使用storm之前,顯示實時統計數據一(yī)般有兩種方案:
在數據庫裏執行count、sum等聚合查詢,是簡單快速的(de)實現方案,但容易出現慢查詢。
在業務代碼裏對統計指标做(zuò)累加,可(kě)以滿足指标的(de)快速查詢,但統計邏輯耦合到業務代碼,維護不方便,而且錯誤數據定位和(hé)修正不方便。
既要解耦業務和(hé)統計,也要滿足指标快速查詢,基于storm的(de)實時計算方案可(kě)以滿足這兩點需求。
一(yī)個storm應用的(de)基本結構有三部分:數據源、storm應用、結果集。storm應用從數據源讀取數據,經過計算後,把結果持久化或發送消息給其他應用。
第一(yī)版的(de)訂單實時統計結構如(rú)下圖。在數據源方面,最早嘗試在業務代碼裏打日志的(de)方式,但總有業務分支無法覆蓋,采集的(de)數據不全。我們的(de)業務數據庫是mysql,随後嘗試基于mysql binlog的(de)數據源,采用了阿裏開源的(de)canal,可(kě)以做(zuò)到完整的(de)收集業務數據變更。
在結果數據的(de)處理(lǐ)上,我們把統計結果持久化到了mysql,并通過另一(yī)個後台應用的(de)RESTful API對外提供服務,一(yī)個mysql就可(kě)以滿足數據的(de)讀寫需求。
為(wèi)了提升實時統計應用吞吐量,需要提升消息的(de)并發度。spout裏設置了消息緩沖區,隻要消息緩沖區不滿,就會源源不斷從消息源canal拉取數據,并把分發到多個bolt處理(lǐ)。
第二版:性能提升
第一(yī)版的(de)性能瓶頸在統計結果持久化上。為(wèi)了确保數據的(de)準确性,把所有的(de)統計指标持久化放在一(yī)個數據庫事務裏。一(yī)筆(bǐ)訂單狀态更新後,會在一(yī)個事務裏有兩類操作:
訂單的(de)曆史狀态也在數據庫裏存着,要與曆史狀态對比決定統計邏輯,并把最新的(de)狀态持久化。storm的(de)應用本身是無狀态的(de),需要使用存儲設備記錄狀态信息
當大家知道(dào)實時計算好用後,各産品都希望有實時數據,統計邏輯越來越複雜。店鋪、商品、用戶等多個指标的(de)寫操作都是在一(yī)個事務裏commit,這一(yī)簡單粗暴的(de)方式早期很好滿足的(de)統計需求,但是對于update操作持有鎖時間過長(cháng),嚴重影響了并發能力。
為(wèi)此做(zuò)了數據庫事務的(de)瘦身:
去(qù)除曆史狀态的(de)mysql持久化,而是通過單條binlog消息的(de)前後狀态對比,決定統計邏輯,這樣就做(zuò)到了統計邏輯上的(de)無狀态。但又産生了新問題,如(rú)何保證消息有且隻有處理(lǐ)一(yī)次,為(wèi)此引入了一(yī)個redis用于保存最近24小時內(nèi)已成功處理(lǐ)的(de)消息binlog偏移量,而storm的(de)消息分發機制又可(kě)以保證相同消息總是能分配到一(yī)個bolt,避免線程安全問題。
統計業務拆分,先是線上業務和(hé)公司內(nèi)部業務分離(lí),随後又把線上業務按不同産品拆分。這個不僅僅是bolt級别的(de)拆分,而是在spout就完全分開
随着統計應用拆分,在canal和(hé)storm應用之間加上消息隊列。canal不支持多消費者,而實時統計業務也不用關系數據庫底層遷移、主從切換等維護工作,加上消息隊列能把底層數據的(de)維護和(hé)性能優化交給更專業的(de)團隊來做(zuò)。
熱點數據在mysql裏做(zuò)了分桶。比如(rú),通常一(yī)個店鋪天級别的(de)統計指标在mysql裏是一(yī)行數據。如(rú)果這個店鋪有突發的(de)大量訂單,會出現多個bolt同時去(qù)update這行數據,出現數據熱點,mysql裏該行數據的(de)鎖競争異常激烈。我們把這樣的(de)熱點數據做(zuò)了分桶,實驗證明在特定場景下可(kě)以有一(yī)個數量級吞吐量提升。
最終,第二版的(de)訂單實時統計結構如(rú)下,主要變化在于引入了MQ,并使用redis作為(wèi)消息狀态的(de)存儲。而且由最初的(de)一(yī)個應用,被拆成了多個應用。