熱點賬戶問題和常用解決方案【中】

話接上回,上篇闡述了什麼是熱點賬戶,基本財務賬戶如何設計,冪等健和鏈式設計!本篇將針對熱點賬戶在實踐中引發的問題,梳理和拆解業務流,分析問題點,提出七種常用解決方案。

一、性能問題初現

上線初期數據量較小,運行正常!
一次大促後,賬戶流水的總數目接近億級別,初現性能問題:系統整體的qps也就10+,但熱點賬戶寫入失敗率偏高,並且隨數據量增加失敗率逐步升高;整個賬戶系統全靠上游有redo標識位不斷重試,才能保證最終寫入成功!

哈哈,作爲一名擁有三年工作經驗的老碼農,面對問題,要做的第一件事,就是,抽根菸靜靜,準備開搞!

二、數據流拆解

拿到問題,抽根菸靜一下之後,分析問題需要三步:梳理數據流,拆解過程,定位問題點。先對財務賬戶更新的數據流進行拆解

finance_hot_account_3

鏈式鎖後的基本賬戶操作過程,分爲如下五階段

  • 請求階段:賬戶操作請求。
  • 查詢階段:查詢當前賬戶信息。主要獲取當前鏈,資金數據等!
  • 計算階段:對鏈和操作的資金進行計算,判定資金操作合規,同時保證冪等和併發!
  • 寫入階段:事務同步寫入流水和餘額
  • 響應階段:告知上游回調

三、鏈路分析

梳理數據流後,接下來分析每個階段可能引發的問題。按照優先級,先分析業務問題區域(讀取階段,計算階段,寫入階段),通常問題會出現在業務階段;之後,再分析框架問題區域(請求階段和回調階段),該區域出現問題的情況偏小,但是一旦出現問題,就是比較有意思^_^!

3.1 業務問題區域分析

讀取階段,計算階段,寫入階段三個階段是具體的業務操作,從併發和耗時兩個角度來分析下可能的問題點

3.2.1 耗時分析

耗時分爲三塊

  • 查詢耗時:RDS擁有億級別數據量,查詢未中primary,但命中索引,業務數據體並未完全在索引中,因此訪問數據走index match;數據主鍵聚簇,唯一健索引查詢獲取數據,page極難命中cache,也不會命中磁盤電梯算法優化!結合實際情況,查詢耗時在10-100ms級別
  • 寫入耗時:insert 包含了自增,理論上在數據落盤是追加寫,即使uniq_key去創建索引的情況下,耗時在ms級
  • 過程耗時:長連接情況下,init conn時間基本可以忽略,但是讀寫兩次往返數據庫的鏈路時間還是需要考慮,整體預估在1-2ms之間

從整體上看,預估該階段的耗時在10-100+ms,從實際失敗率來看也基本一致!

3.2.2 併發分析

  • 天級QPS:當時分析天級幾十萬單,天級QPS不到10,不高!
  • 瞬間QPS:每個訂單拆解到資金流後,會同時操作多次熱點賬戶,瞬間qps相對很高,理論qps就可能達到語言上限,由於上游鏈路限流1024,按照10級別操作拆分,理論上滿池QPS在萬級別。考慮實際單量,瞬間QPS=單量(10)*拆解量(10),實際的滿額預估QPS可能到100+ !

按照上面分析,在瞬時QPS達到10+的情況下,熱點賬戶整體延時在10-100+ms,由於DB在寫入uniq_key保證鏈點唯一,所以出現併發寫入失敗也在情理之中;並且隨着數據量的提升,讀取延時增加,寫入失敗率會繼續增加。

3.2 框架問題區域

請求階段做爲入口,一般也分爲三個小階段

  • webserver接收請求
  • 框架加載和路由
  • 基礎校驗

請求階段核心耗時一般存在於框架加載和路由,高併發場景webserver和upstream之間的調用也是一個可能出問題點!當時財務系統,採用歡總封裝的go-thrift,並且其他模塊並未出現請求階段問題,所以並未對這個階段的latency和併發做一個衡量,重點分析了業務模塊!

四、解決方案

4.1 讀取和寫入階段優化

通過上面分析,目前問題的痛點是併發讀取熱點賬戶數據高延時引發寫入失敗,提升讀性能成爲了關鍵

讀性能提升有兩個基本思路:讀的時效快和讀的次數少

針對上面兩個基本思路,結合財務賬戶情況提出了五種提升讀性能的解決方案

  • 【讀快】持久化last record:不從全量數據裏面讀,抽離子賬戶的最新信息,持久化到單獨的表中或者內存中,降低整體數據量,提升了讀性能。缺點是要保證持久化信息的準確性,引入事務寫。
  • 【讀快】縱向切分-時間分庫分表:按照時間進行縱向切分,降低查詢範圍內的數據量,提升讀性能。缺點是跨時間讀不友好,開發量也不小
  • 【讀快】縱向切分-歸檔:歷史數據歸檔是實現相對簡單,跨時間讀也比較友好,隨着數據量的提升,也是必須要做,之後會詳細介紹歸檔方案和選型。
  • 【讀快】橫向切分-業務分庫分表:按照賬戶類型或者城市分庫分表,可以優化讀寫數據量,同時,跨表讀負擔也會較小。但對於熱點賬戶或者熱點城市,依然聚簇,效果不是很明顯。同時,再次對熱點賬戶進行橫向分庫分表也是極度不推薦,引入的極高的讀寫成本均。
  • 【讀少】階段快照:一定量或者一定時間內的數據,持久化一次。優勢是極大的降低讀寫次數;缺點是需要複雜的邏輯來保證undo操作和數據一致性!

五種解決方案各有千秋,作爲一個初期的財務系統推薦採用持久化last record和數據歸檔來保證寫入讀性能和整體讀的數據量。如果系統發展到了中期,推薦按照時間分庫分表。如果發展到了雙11或者春晚某些極端場景,犧牲掉部分準確性,採用階段快照也是可以的。

4.2 計算階段優化

存在計算階段造成的最大影響也就是引起了兩次數據傳輸,通常是不可避免的,但是如果真的是要進行提升有一種方案通用方案

  • DB計算通過存儲計算,轉嫁計算成本給DB,減少一次鏈路請求。但不太推薦,複雜的sql往往有坑,insert computer from select 還會造成大面積的數據隔離,很容易引起死鎖。

4.3 請求和回調階段優化

請求階段一般有三種形式:同步調用,異步調用和僞同步調用
前兩種調用非常常見:同步爆池的情況,一般採用限流來降壓,採用漏桶,令牌桶等等策略;異步調用通常採用消息隊列來削峯填谷;這裏重點闡述對於支付和財務系統在請求階段經常採用的僞同步的方式

僞同步流量較早出現在innodb,leveldb等存儲引擎爲了利用追加寫提升寫入性能,採用類WAL日誌來持久化數據。通常僞同步方案採用三件套:WAL日誌+校驗位+廣播消息來完成一次完整的請求!流程圖一般如下

finance_hot_account_4

  • 請求階段:同步請求調用,核心要素追加寫入wal日誌,變更校驗位,完成同步調用!此處追加寫保證了快速寫入,校驗位來保證數據的最終寫入成功。圖中1,2
  • 異步階段:通過讀取wal日誌的核心數據,進行復雜事務處理,如果成功進入下一階段;如果失敗,沒問題,通過外部trigger來觸發redo操作!如果多次redo依然失敗,那麼通過undo來回滾數據
  • 回調階段:如果成功,更改校驗位,同時發佈成功廣播消息,關注結果和時效性的模塊,可以獲取最終成功的標識!如果undo回滾數據,則發佈失敗廣播消息,告知結果失敗!

在僞同步的模式下指標衡量:

  • QPS:僞同步模式,採用WAL核心要素追加寫,所以寫性能可以極大提升,進而滿額QPS相對直接同步調用也大量提升
  • 時效性:僞同步並非完全同步,所以結果需要監聽回調。對於結果強一致的請求,必須監聽回調,確保一致,時效性降低;對於弱一致可以認爲同步回調即成功,時效性提升。
  • 失敗率:操作知識核心要素追加寫入,真正的操作通過異步保證,整體成功率提升!

對於資金處理過程,大量採用僞同步的請求方式來保證快速寫入和最終一致性

4.4 解決方案總結

總的來說,歸結了七種優化方式(哈哈,上篇寫的八種優化,當時總結的,現在愣是想不到還有啥了^_^)。其中請求和回調的僞同步方式,是在架構層面優化,這個在多數的財務系統和財務系統的內部數據流中都會用到;而讀寫和計算階段的優化,可以跟進實際業務情況進行選型處理。

五、事故覆盤

面對各種優化方案,需要結合實際情況做出取捨,有的是長期方案,有的是快速方案,但一定需要想清楚了再開搞,過程中有一個對小拽之後影響很大的事故,引以爲戒。

翻車過程:當時覺的讀->計算->寫這個過程,兩次讀DB操作,下沉計算過程到DB後,通過DB計算,可以減少一次數據庫請求。於是合併了一個大SQL,也就是所謂的insert ( field computer from select),覺的寫了個狂賺酷炫吊炸天的SQL,一上線,庫鎖死了!幸好有前置的redo flag,全量redo數據恢復,要不然估計直接祭天了!

對於這個複雜大SQL事故,小拽總結了三個方面

莫炫技:沒啥好說的,解決問題的成就感要遠大於炫技!
簡單設計:簡單的設計通常意味着可依賴;複雜的設計要儘可能的拆解,想清楚,隊友都理解不了的設計,那就別上線了,可能真的需要再思考拆解下
尊重線上:核心服務基本上線流程一定要遵守,測試,監控和回滾方案缺一不可

六、小結

本篇主要針對熱點賬戶問題提出了七種常用的解決方案,下篇將繼續引申探索下,各種解決方案在不規則高併發場景,例如雙十一,微博熱點事件中如何套用

預知後事如何,下回再聊!

【轉載請註明:熱點賬戶問題和常用解決方案【中】 | 靠譜崔小拽

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章