項目背景
????????以一個積分系統為例,積分系統最核心的有積分賬戶表和積分明細表:
- 積分賬戶表:每個用戶在一個品牌下有一個積分賬戶記錄,記錄了用戶的積分余額,數據量在千萬級
- 積分明細表:用戶每次積分發放、積分扣減、積分退款、積分過期都會產生一條積分明細,積分明細的數據量很大在億級別
? ? ? ? 隨著業務的增長、時間的推移,積分明細的數據量越來越多,單表數據量太大,產生很多問題:慢SQL,加字段、索引比較耗時(雖然可以用MySQL8.0的新特性INSTANT算法加字段),統計查詢很慢,數據庫CPU壓力大等
一、為什么要選擇分表
????????目前積分系統的積分明細數據現存幾億條,每日新增積分明細數據大幾十萬,并且隨著活動的接入還在逐漸遞增,慢慢的帶來了很多問題,需要進行優化處理。
? ? ? ? 歷史數據歸檔不行嗎
????????因為系統積分規則規定積分有效期兩年,并且積分扣減、退款、過期等操作需要對積分明細溯源,最近兩年的積分明細數據也有幾個億,即使將兩年前的冷數據歸檔,也無法解決積分明細單表幾億數據量的問題。
????????根據積分現有數據量以及日增長量進行評估,未來3-5年內單數據庫實例能夠滿足積分數據的存儲,并且積分系統的寫入TPS單實例可以支撐,本次積分決定只分表不分庫。
? ? ? ? 什么情況適合用數據歸檔?
????????如果能把歷史冷數據歸檔,熱數據一兩億左右、讀寫并發不太高的情況下,利用好數據庫索引、數據庫配置高一些完全可以不用分表,采用歷史數據歸檔也可以解決很多問題。但是大表還是有一些其他的問題,比如加字段比較耗時。
????????分庫分表有很多問題
????????網上動不動就說超過2000萬數據就要分庫分表并不太對,在單表數據一兩億、并發不是很高、利用好數據庫索引的情況下,MySQL數據庫配置高一點是完全可以扛得住的。
????????而且能不分庫分表就不要分庫分表,分庫分表會產生很多問題,沒有其他優化手段了再進行分庫分表。分庫分表可能會產生的問題如下:
- 分布式ID問題
- 分片鍵選擇問題
- 分庫分表算法問題
- 容量不夠了擴容問題
- 分布式事物問題
- 統計查詢分析問題
- 數據遷移問題
- 灰度驗證問題
- 數據校驗問題
- ................
二、分庫分表組件
????????分庫分表組件選擇Sharding-JDBC,因為目前該項目文檔較為豐富、社區活躍度高、無中心化、性能相較于proxy方式性能更好,對于開發來說使用更為靈活可控。官網地址Apache ShardingSphere
三、分多少張表,分片鍵和分片算法
????????需要拆分的表:point_info(積分明細表)
????????根據目前的數據量、單日新增的數據量來進行分析,計劃分為128張表,未來3-5年內單表數據盡量不要超過2000萬,后期分表數據量大了可以進行歷史數據歸檔。
????????拆分后的表為point_info[0-127],采用user_id作為分片鍵,選取user_id后四位取模定位到具體的分表
????????table后綴 = (user_id后四位) % 128
? ? ? ? 如果怕user_id的尾數不均勻,可以將hashcode(user_id)%128來計算分表下標
????????分表數量為啥是128,不是127或者100?
????????我自己的理解如下:
- 設置為2的次冪,是程序員的習慣;
- 好處是求余運算,可以用 num & (128-1),按位與運算求余數比除法速度快。
????????如果還有其他的好處歡迎指正
四、分布式事務?
????????因為只進行了分表,沒有進行分庫,所以沒有分布式事務問題。
????????假如后面進行了分庫分表,可以通過將相同用戶的積分賬戶表和積分明細表分到同一個分庫中,來避免同一個用戶操作賬戶和明細的分布式事務問題。
????????shardingsphere里面也有一些分布式事務的支持,比如XA,Seata框架的AT模式等
五、分布式ID
????????原來單表時主鍵采用MySQL自增id,分表之后再使用自增id會導致不同表主鍵值重復,可以使用雪花算法、美團的Leaf等生成分布式ID,也可以自定義實現。
????????注意:原生雪花算法有時鐘回撥問題、低頻場景下生成的id都是偶數的問題,需要進行優化一下,感興趣的話可以看下我的另一篇博客?雪花算法生成分布式ID源碼分析及低頻場景下全是偶數的解決辦法
六、總體計劃
簡要描述一下整個流程:
線上庫新的分表創建配置完成,然后按照下面的步驟執行:
- 改造雙寫代碼預發測試(多種case跑一下,雙寫開關等校驗),沒問題發布上線,上線時雙寫開關默認關閉,可以通過配置中心動態開啟,打開雙寫開關(新表寫入失敗先忽略,因為更新和刪除操作會因為新表數據不存在而失敗),記錄雙寫開始時間點A
- 將老表的積分明細的createTime小于等于雙寫開始時間點A+5分鐘(防止時間不同步導致少遷移數據,預留一些緩沖時間)的數據進行全量遷移到分表
- 新老數據全量數據校驗,查看數據是否一致;同時定時任務每隔一小段時間進行增量校驗,增量數據因為讀取新老數據存在短暫時間差可能會瞬時不一致,這種數據隔一段時間再次校驗,多次校驗還不一致的數據進行數據訂正(老表數據覆蓋到新表數據)
- 改造代碼,添加雙讀的邏輯上線(讀新表的開關默認關閉)
- 低流量節點(凌晨過后)進行白名單、灰度切流userId%10000,進行驗證,逐步流量打開,持續觀察
- 雙寫開關切到新表,保證只寫新表(也可以繼續寫老表一段時間,或者創建一個新表往老表同步的canal任務,方便回滾),完成數據遷移方案
- 系統穩定運行一段時間,遷移&雙寫代碼下線,老表進行資源釋放
????????為什么沒有采用全量同步+canal增量同步方式,這種方式不是比雙寫實現更簡單嗎?怎么進行雙寫?雙寫有什么好處?后面會繼續寫一篇單獨介紹。
七、雙寫
? ? ? ? 雙寫改造點:增、刪、改
????????雙寫主要是為了避免數據延遲問題,但是雙寫無法保證兩個數據源的事物一致性問題,極端情況下數據不一致(數據校驗訂正任務解決)
雙寫開關有兩個(通過配置中心實時切換):
- 寫老表開關:默認開啟,新表寫入沒有問題時可以進行關閉,也可以繼續寫一段時間老表
- 寫新表開關:默認關閉,需要開啟時打開
????????新老表的開關同時打開時,表示要進行雙寫
通過配置中心動態進行切換,雙寫期間需要注意的問題如下:
- 對寫新表操作需要記錄日志
- 新表不要求一定寫成功(不影響服務,記錄錯誤日志告警通知等,有數據校驗訂正任務兜底)
- 雙寫事物回滾:極端情況下可能新表寫成功了、老表數據事物回滾了(數據雙向核對校驗)
八、數據遷移
????????首先我們程序會打開雙寫,然后需要將我們數據庫已經存在的數據進行批量的全量遷移,在這一過程中我們需要不斷的進行數據校驗。當我們校驗基本問題不大的時候,然后進行切流操作,直到完全切流之后,我們就可以不用再進行數據校驗。
1)全量同步
- 全量遷移:將老表的積分明細的createTime小于等于雙寫開始時間點A+5分鐘(防止時間不同步導致少遷移數據,預留一些緩沖時間)的數據進行全量遷移
- 全量默認走備庫查詢,目標端寫入的是主庫,防止影響主庫讀寫,這里需要注意一下讀寫的qps,以免對線上服務造成影響。
????????注意一下存量數據遷移完成的時間,存量數據遷移可以多線程執行,計算公式如下:
消耗小時數= 數據總量 / (3600*寫入tps*任務線程數)
2)增量同步
????????因為我們代碼使用了雙寫,雙寫自動做了增量同步,所以不需要單獨做增量同步的操作了
九、數據校驗
????????驗證老的單表和新的分表的數據是否一致,也是全量訂正服務必須的前置操作
全量校驗&增量校驗&抽樣校驗
- 全量校驗:歷史全量數據校驗
- 增量校驗:定時任務每隔一段時間校驗該段時間內的增量數據校驗是否一致
- 抽樣校驗:可以按照user_id或者業務類型等自定義校驗
校驗過程
????????需要注意:校驗任務注意不要影響線上運行的服務,通常校驗任務會寫很多批查詢的語句,會出現批量掃表的情況,如果代碼沒有寫好很容易導致數據庫掛掉。
????????對賬標準:老的單表和新的分表中數據保持一致(所有字段或者核心字段)
????????對賬流程:通過定時任務輪詢+監聽數據庫binlog執行已經完成遷移的用戶在新老積分明細表的數據一致性。需要注意的是由于讀取新老庫有先后順序,所以產生瞬時的數據不一致,對于這種問題可以采用對賬重試,只要保證最終一致即可。
數據雙向核對
????????單表核對多表,多表核對單表。原因是雙寫時無法完全控制事物一致性問題,所以要對單表和分表進行雙向核對,具體怎么核對后面會再寫一篇進行詳細介紹。
校驗結果
? ? ? ? 校驗結果:差異、缺失、多出
數據訂正
- 通過訂正服務可以將不一致的老的單表數據和新的分表數據進行數據訂正,保證一致性。
- 使用訂正服務前必須進行校驗服務。
十、切流量
切流代碼編寫,切流量是針對的積分明細的查詢操作
- 對所有查詢接口進行整理
- 讀取新老數據配置開關,可以通過配置中心實時修改
- 添加驗證白名單列表(userId)
- 根據白名單、userId后4位取模進行灰度,動態獲取查詢時用的數據源。
- 命中灰度時,查詢走新分表數據源,查新表數據,同時也查詢老表數據,比對新老表數據是否一致,如果一致返回新表數據,如果不一致返回老表數據,同時發送MQ信息對該用戶數據進行數據校驗、比對、訂正。
????????切流過程采用逐步放量的形式,灰度方式很多,我們采用的是先白名單驗證,然后用戶ID取模10000逐步放量的方式。灰度切流驗證:萬分之1-1%-5%-10%-50%-100%切流
? ? ? ? 關于灰度驗證、切流的過程后續會再單獨寫一篇細化介紹一些。
十一、收尾
????????直到切流到100%,繼續運行一段時間,然后觀察各個業務后續工單反饋情況和各個系統預警&日志;
????????切寫開關到新的分表上面,可以繼續寫入老表一段時間,如果出現問題可以切回老表
????????對新表進行性能壓測,確保新表的穩定性
十二、OLAP數據統計分析
????????對于積分的數據報表、統計查詢分析等需求,由于分表后數據分散到了多個分表上面,如果要再進行統計查詢的話需要查詢所有的分表,效率太低了,可以使用一些非關系型數據庫,比如ES、ClickHouse等。
? ? ? ? 比如可以使用ClickHouse,將多個分表的數據同步聚合到ClickHouse上面,利用ClickHouse進行統計分析、出一些數據報表等。
? ? ? ? 本篇博客從整體上介紹了一個完整的積分明細分表的實施方案,里面有一些方面介紹的不是很詳細,后面會單獨的針對一些關鍵點進行介紹:
- 怎么進行代碼雙寫,雙寫有什么好處,為什么沒有使用數據庫binlog增量同步
- 怎么進行數據雙向校驗
- 怎么進行灰度驗證,怎么進行切流
- .........
? ? ? ? 如果有寫得不對的地方,或者您有更好的方案,歡迎討論指正