金幣(積分)商城(下稱「商城」)是衆多App內的一個產品,隨着App使用的用戶愈來愈多,商城對於用戶留存的提高,扮演着重要的角色;作爲提升用戶黏性的核心產品,在擁有很好用戶體驗的同時,也必須存在着一個高效、穩定的系統。node
商城,是一個基於虛擬貨幣(下稱「金幣」)進行運營的產品,也就意味着,咱們須要給用戶發放金幣,用於用戶兌換各類獎品。咱們須要詳細記錄用戶金幣的收支狀況,並提供給用戶查詢。在redis、memcache盛行的時代,構建一個幾十萬QPS的只讀系統並不複雜,須要作到:無狀態服務+多級緩存,而且可以進行水平擴展,應該就差很少了。而商城須要記錄每秒十萬的用戶行爲,須要的是每秒數十萬(這裏翻倍了)的數據讀寫(insert&update)操做,這種量級是沒法在單實例數據上完成的,那麼該如何分庫分表。redis
Tip 1 . 在作設計時,首先要明確3個事情算法
- 業務場景,不要空談設計,場景是什麼
- 目標,系統須要作到的目標是什麼
- 分析上述兩點,獲得什麼結論
那麼,對於用戶行爲數據的場景是:1.用戶A查詢本身的全部金幣記錄(不能查別人的),2.查看商城內金幣數量分佈狀況。對於第二個場景能夠直接經過大數據進行統計分析(不進行詳細解讀)。對於第一個場景,系統須要作到的目標是:用戶A只進行一次查詢,就能夠獲得全部數據(不考慮分頁場景)。分析上述兩點,獲得結論:按用戶id進行分庫分表。(分析過程有些磨嘰了,哈哈,忍着)
原則明確後,可以開始進行分庫分表嗎?不能。須要進一步確認,如何分?分多少?擴容成本?對於數據庫擴容,咱們選擇以2的N次冪進行擴容,這種方式的好處是,進行擴容時,只須要將數據copy一份就能夠,上層應用增長數據庫節點,無需考慮數據遷移問題(可靠性高),很差的地方是,會產生髒數據,這個問題並沒太多影響,按照擴容後規則,刪除便可。對於分表,咱們將金幣記錄在每一個庫中拆成5份。數據庫
Tip 2 .爲何要進行分庫分表segmentfault
- 服務器資源(cpu、磁盤、內存、IO)遇到瓶頸
- 數據量變大,數據操做(crud)開銷變大
部署圖以下:緩存
數據編號=uid%4,表編號=uid%5服務器
算法流程圖以下:併發
目前業內對分庫實現方案有兩種異步
這兩種方案各有利弊,客戶端分庫分表因爲直連數據庫,因此性能比使用中間件要高。而使用中間件,可以很好的將分庫分表操做與客戶端隔離,數據調整對上層透明,便於統一管理。分佈式
爲何要關注id生成策略?全局惟一,全局有序,業務隔離,不容易被猜到等等,這些都不是關鍵。重點討論下,如何讓看似無心義的id,對系統後續擴展帶來意義。
Java領域著名的惟一id應該算是uuid了,不過uuid太長,並且包含字母,不適合作爲訂單id。經過調研,咱們借鑑了Twitter的Snowflake算法來實現,算法思想是在64位長整型數字中,存儲node編號,而且有序,同時支持併發。
爲了便於訂單數據後期擴展,咱們有必要在訂單id生成時,就將其作好分庫分表準備(雖然目前訂單量很少)
其中serverid,佔2位,最大支持2^2臺服務器(0-3),dbid佔6位,最大支持2^6個數據庫,其餘以此類推。
訂單數據除了用戶維度查詢外,還有經過商品維度來查詢的場景,例如:按照商品,進行訂單發貨。爲了解決這個問題,咱們對應的策略是,將訂單數據進行冗餘,並按照商品維度進行存儲。方案雖然簡單,可是保持兩個訂單庫數據的一致性是一件很麻煩的事情。
顯然單機數據庫事務是沒法解決的(數據不在同一個數據庫中),因此要保證數據一致性,須要引入強一致性的分佈式事務,這個方案先不談實現成本問題,就憑其超低的效率,這是咱們沒法接收的。因此引入異步數據同步,來實現數據的最終一致性。固然,異步同步數據也會帶來數據不一致(消息總線丟消息,嘿嘿),因此咱們又引入了實時監控服務,實時計算數據差別,並進行一致性同步。
流程圖以下:
Tip 3 . 相似這種存在多個緯度的數據存儲問題,均可以採用這種方案來解決
這是個經典的議題了,咱們在這個方案上,並沒有創新,用幾張圖來簡單說明下。
如何讓商城在大流量下存活?這是一個相似搶購或者秒殺場景如何應對的問題,對於這個問題在@沈劍 的《秒殺系統優化思路》中已經寫的很清晰了,那麼,我再補充一下。
中心思路路仍然是」逐層消耗流量「,應用層面對大流量狀況下,頗有可能自身難保,還沒來得及攔截流量,自身就已經OOM了。那麼該如何優化這個方案?見下圖:
在ngx層進行優化,有兩個方案:
咱們採用的第二個方案。視頻課程
==【完】==