天天看點

金币(積分)商城架構漫談

開篇

金币(積分)商城(下稱“商城”)是衆多App内的一個産品,随着App使用的使用者越來越多,商城對于使用者留存的提升,扮演着重要的角色;做為提高使用者黏性的核心産品,在擁有很好使用者體驗的同時,也必須存在着一個高效、穩定的系統。

分庫分表

商城,是一個基于虛拟貨币(下稱“金币”)進行營運的産品,也就意味着,我們需要給使用者發放金币,用于使用者兌換各種獎品。我們需要詳細記錄使用者金币的收支情況,并提供給使用者查詢。在redis、memcache盛行的時代,建構一個幾十萬QPS的隻讀系統并不複雜,需要做到:無狀态服務+多級緩存,并且能夠進行水準擴充,應該就差不多了。而商城需要記錄每秒十萬的使用者行為,需要的是每秒數十萬(這裡翻倍了)的資料讀寫(insert&update)操作,這種量級是無法在單執行個體資料上完成的,那麼該如何分庫分表。

分庫分表原則

Tip 1 . 在做設計時,首先要明确3個事情
  1. 業務場景,不要空談設計,場景是什麼
  2. 目标,系統需要做到的目标是什麼
  3. 分析上述兩點,得到什麼結論

那麼,對于使用者行為資料的場景是:1.使用者A查詢自己的所有金币記錄(不能查别人的),2.檢視商城内金币數量分布情況。對于第二個場景可以直接通過大資料進行統計分析(不進行詳細解讀)。對于第一個場景,系統需要做到的目标是:使用者A隻進行一次查詢,就可以得到所有資料(不考慮分頁場景)。分析上述兩點,得到結論:按使用者id進行分庫分表。(分析過程有些磨叽了,哈哈,忍着)

原則明确後,能夠開始進行分庫分表嗎?不能。需要進一步确認,如何分?分多少?擴容成本?對于資料庫擴容,我們選擇以2的N次幂進行擴容,這種方式的好處是,進行擴容時,隻需要将資料copy一份就可以,上層應用增加資料庫節點,無需考慮資料遷移問題(可靠性高),不好的地方是,會産生髒資料,這個問題并沒太多影響,按照擴容後規則,删除即可。對于分表,我們将金币記錄在每個庫中拆成5份。

Tip 2 .為什麼要進行分庫分表
  1. 伺服器資源(cpu、磁盤、記憶體、IO)遇到瓶頸
  2. 資料量變大,資料操作(crud)開銷變大

部署圖如下:

金币(積分)商城架構漫談

算法

資料編号=uid%4,表編号=uid%5

算法流程圖如下:

金币(積分)商城架構漫談

目前業内對分庫實作方案有兩種

  1. 用戶端分庫分表,在用戶端完成分庫分表操作,直接連結資料庫。
  2. 中間件(例如:cobar),用戶端連結中間件,由中間件完成分庫分表操作。

這兩種方案各有利弊,用戶端分庫分表由于直連資料庫,是以性能比使用中間件要高。而使用中間件,能夠很好的将分庫分表操作與用戶端隔離,資料調整對上層透明,便于統一管理。

訂單id生成政策

為什麼要關注id生成政策?全局唯一,全局有序,業務隔離,不容易被猜到等等,這些都不是關鍵。重點讨論下,如何讓看似無意義的id,對系統後續擴充帶來意義。

Java領域著名的唯一id應該算是uuid了,不過uuid太長,而且包含字母,不适合做為訂單id。通過調研,我們借鑒了Twitter的Snowflake算法來實作,算法思想是在64位長整型數字中,存儲node編号,并且有序,同時支援并發。

為了便于訂單資料後期擴充,我們有必要在訂單id生成時,就将其做好分庫分表準備(雖然目前訂單量不多)

金币(積分)商城架構漫談

其中serverid,占2位,最大支援2^2台伺服器(0-3),dbid占6位,最大支援2^6個資料庫,其他以此類推。

最終一緻性

訂單資料除了使用者次元查詢外,還有通過商品次元來查詢的場景,例如:按照商品,進行訂單發貨。為了解決這個問題,我們對應的政策是,将訂單資料進行備援,并按照商品次元進行存儲。方案雖然簡單,但是保持兩個訂單庫資料的一緻性是一件很麻煩的事情。

顯然單機資料庫事務是無法解決的(資料不在同一個資料庫中),是以要保證資料一緻性,需要引入強一緻性的分布式事務,這個方案先不談實作成本問題,就憑其超低的效率,這是我們無法接收的。是以引入異步資料同步,來實作資料的最終一緻性。當然,異步同步資料也會帶來資料不一緻(消息總線丢消息,嘿嘿),是以我們又引入了實時監控服務,實時計算資料差異,并進行一緻性同步。

流程圖如下:

金币(積分)商城架構漫談
Tip 3 . 類似這種存在多個緯度的資料存儲問題,都可以采用這種方案來解決

資料庫高可用

這是個經典的議題了,我們在這個方案上,并無創新,用幾張圖來簡單說明下。

金币(積分)商城架構漫談
金币(積分)商城架構漫談
金币(積分)商城架構漫談

Hold住流量

如何讓商城在大流量下存活?這是一個類似搶購或者秒殺場景如何應對的問題,對于這個問題在@沈劍 的《秒殺系統優化思路》中已經寫的很清晰了,那麼,我再補充一下。

中心思路路仍然是”逐層消耗流量“,應用層面對大流量情況下,很有可能自身難保,還沒來得及攔截流量,自身就已經OOM了。那麼該如何優化這個方案?見下圖:

金币(積分)商城架構漫談

在ngx層進行優化,有兩個方案:

  1. 達到應用層最大處理能力時,Hold住流量,讓請求排隊,逐漸施放流量到應用層。
  2. 達到應用層最大處理能力時,抛棄多餘流量。

我們采用的第二個方案。視訊課程

金币(積分)商城架構漫談

==【完】==