一刻剥、背景
訂單分庫分表不同于其他業(yè)務(wù)表分庫分表,訂單表的讀寫場景復(fù)雜滩字,一般有買家維度造虏、賣家維度御吞、訂單號維度3個主要維度。多讀寫維度情況下無論采取哪種維度做分庫分表漓藕,對另外兩種維度的查詢性能來說陶珠,基本都是災(zāi)難。而在電商項目中撵术,每個訂單從創(chuàng)建到終態(tài)往往都會經(jīng)歷上百個業(yè)務(wù)環(huán)節(jié)背率,任何一個環(huán)節(jié)出問題就會導(dǎo)致整單數(shù)據(jù)異常话瞧。所以交易系統(tǒng)可能不是技術(shù)難度最深的系統(tǒng)嫩与,但一定是業(yè)務(wù)復(fù)雜度最高、穩(wěn)定性要求最高的系統(tǒng)
二交排、訂單單表面臨的問題
訂單單表數(shù)據(jù)量大了之后一般會面臨以下問題:
- 單表過大划滋,SQL RT過高
- 數(shù)據(jù)庫實例負載過高
- 單庫實例磁盤空間有限
以上都是比較嚴重的問題,如果不予以解決埃篓,將會嚴重影響機器性能处坪、限制業(yè)務(wù)發(fā)展
應(yīng)對以上問題一般常用的優(yōu)化思路如下:
- 優(yōu)化SQL
- 優(yōu)化索引
- 優(yōu)化業(yè)務(wù)
- 讀寫分離
- 復(fù)雜查詢借助ES
僅通過以上優(yōu)化方式,數(shù)據(jù)量幾千萬后架专,業(yè)務(wù)高峰期還是頂不住同窘,壓測也壓不上去,這個時候就不得不采取分庫分表措施了部脚。
三想邦、拆分類型介紹
3.1 垂直(縱向)拆分
根據(jù)業(yè)務(wù)特性,與拆分微服務(wù)的做法相似委刘,將關(guān)聯(lián)度低或數(shù)據(jù)量巨大的不同表存儲在不同的數(shù)據(jù)庫丧没,或者將不經(jīng)常用或字段長度較大的字段拆分到擴展表中。
垂直拆分的優(yōu)點:
- 降低業(yè)務(wù)表之前的耦合度锡移,層次分明
- 一定程度上提升數(shù)據(jù)庫資源的性能瓶頸
垂直拆分的缺點:
- 單表數(shù)據(jù)量過大的問題無法解決
- 拆分過程對歷史業(yè)務(wù)改造較大
3.2 水平(橫向)拆分
將大表按照某個字段采取不同的路由函數(shù)分散到多個數(shù)據(jù)庫或多個表中呕童,表字段完全一致,每個表中只包含一部分數(shù)據(jù)淆珊,所有表加起來等于全量數(shù)據(jù)夺饲。
水平拆分的優(yōu)點:
- 有效解決單表數(shù)據(jù)量過大的問題,提升系統(tǒng)穩(wěn)定性和負載能力
- 業(yè)務(wù)層面改造較小
水平拆分的缺點:
- 所有業(yè)務(wù)SQL安全起見必須帶上分表鍵施符,如果不帶分表鍵查詢會掃全表钞支,性能極低
- 需要自行解決全局唯一ID問題
- 跨表查詢性能較差,只能通過接口聚合方式解決操刀,提升了開發(fā)的復(fù)雜度
四烁挟、常用分片算法
4.1、按時間維度(年/月/日)
比如按年/日分表骨坑,同一天的數(shù)據(jù)都能落在同一張分表撼嗓,后續(xù)訂單的查詢條件都必須帶上時間屬性
按時間維度拆分適用場景:
- 業(yè)務(wù)跟時間關(guān)聯(lián)度高
- 同一時間段內(nèi)業(yè)務(wù)數(shù)據(jù)量分布比較均勻
- 適用于報表柬采、統(tǒng)計系統(tǒng)相關(guān)的業(yè)務(wù)
可行性分析:
按時間分庫分表比較難做,由于訂單業(yè)務(wù)邏輯繁多且警,不可能所有業(yè)務(wù)都有時間作為劃分粉捻,即使帶上時間屬性,業(yè)務(wù)查詢時數(shù)據(jù)庫壓力分布也是在很小范圍內(nèi)斑芜,并不能減輕實例負載肩刃,所以并不適合訂單分庫分表的場景。時間可以作為后續(xù)做冷熱分離的條件杏头。
4.2盈包、HASH
HASH函數(shù)的算法是簡單取模,若分庫和分表使用不同拆分鍵進行HASH時醇王,則根據(jù)分庫鍵的鍵值直接按分庫數(shù)取模呢燥,如果鍵值是字符串,則字符串會先被換算成哈希值再進行路由計算寓娩。若分庫和分表都使用同一個拆分鍵進行HASH時叛氨,則根據(jù)拆分鍵的鍵值按總的分表數(shù)取模。
以t_user表為例棘伴,拆分成8個庫寞埠,每個庫8張表:
HASH適用場景:
- 一個分表鍵夠用
- 適合于需要按用戶ID或訂單ID進行分庫分表的場景
- 適合于拆分鍵是數(shù)字、字符串類型的場景
可行性分析:
采用HASH函數(shù)進行分庫分表焊夸,考慮到訂單會有按買家仁连、按賣家、按訂單號3種主要查詢場景淳地,就需要分別以買家ID怖糊、賣家ID、訂單號3個維度去做3個不同的分庫分表颇象。這種分庫分表方式可行伍伤。但是數(shù)據(jù)就會有3份,存儲是需要考慮先按哪個維度遣钳、再按哪個維度扰魂、最后按什么維度,所以數(shù)據(jù)存儲流程拉長了蕴茴,出異常的可能性變大劝评,對業(yè)務(wù)不太友好
4.3、RANGE_HASH
選取兩個拆分鍵倦淀,兩個拆分鍵的后N位需確保一致蒋畜,根據(jù)任一拆分鍵后N位計算哈希值,然后再按分庫數(shù)取模撞叽,完成路由計算姻成。此路由方式需要自行實現(xiàn)分片算法
以t_order表為例插龄,拆分成8個庫,每個庫8張表:
RANGE_HASH適用場景:
- 兩個拆分鍵有關(guān)聯(lián)
- 查詢時有其中一個拆分鍵值的場景
可行性分析:
如果采取RANGE_HASH函數(shù)作為分庫分表科展,則最優(yōu)方案是以訂單號和買家id的后N位做分庫分表均牢,后續(xù)按訂單號維度、買家id維度查詢都能滿足才睹,賣家維度無法查詢徘跪。但是前提是訂單號后幾位和買家id要有關(guān)聯(lián),涉及到訂單號改造的過程琅攘。
五垮庐、拆分工具
ShardingSphere
在拆分工具選擇上,這里借助ShardingSphere官網(wǎng)乎澄,ShardingSphere是一套開源的分布式數(shù)據(jù)庫解決方案組成的生態(tài)圈突硝,它們能提供數(shù)據(jù)分片测摔、分布式事務(wù)置济、分布式治理等功能。
如果對ShardingSphere原理锋八、使用感興趣浙于,大家可以去ShardingSphere官網(wǎng)進一步學(xué)習(xí)
六、訂單分庫分表
6.1挟纱、訂單表主要服務(wù)的讀寫場景
- 根據(jù)買家id查詢
- 根據(jù)賣家id查詢
- 各個業(yè)務(wù)環(huán)節(jié)羞酗、下游應(yīng)用中大量使用的根據(jù)訂單號讀寫
6.2、訂單表適用的分表方式
通過對訂單表讀寫場景紊服、不同分表函數(shù)檀轨、改造成本的分析,訂單表采用水平拆分適用的分表方式如下:
- 用HASH函數(shù)欺嗤,就需要以買家参萄、賣家、訂單號3個維度拆分成3張邏輯表煎饼,讀寫時根據(jù)場景判斷走哪個表讹挎,數(shù)據(jù)存儲鏈路較長
- 用RANGE_HASH函數(shù),以訂單號和買家id的后N位做分庫分表吆玖,能滿足訂單號和買家id兩個維度的查詢筒溃,賣家id維度無法查詢
6.3、最終采取方案
綜上沾乘,因為訂單一般都是由買家發(fā)起生成的怜奖,所以我們認為優(yōu)先處理買家數(shù)據(jù)和訂單號數(shù)據(jù)比較合理,所以我們先采用RANGE_HASH拆分算法按買家id后N位翅阵、訂單號后N位維度做分庫分表歪玲,作為買家表邏輯表尽爆。再用HASH拆分函數(shù)按商家id冗余一份數(shù)據(jù),作為賣家表邏輯表
- 買家?guī)於辽鳌①I家表:下單填充買家表數(shù)據(jù)漱贱,用于以訂單號、以買家id查詢
- 賣家?guī)熵参①u家表:異步冗余一份買家表數(shù)據(jù)到賣家表幅狮,用于根據(jù)賣家id查數(shù)據(jù)
6.4、訂單號生成
訂單號生成規(guī)則需要根據(jù)買家表分表特性訂單號后N位等于買家Id后N位做設(shè)計
比如用戶id為12345678株灸,則用戶在下單時生成的單號為:xxxxxxxxx345678崇摄,單號前幾位可以根據(jù)公司自己規(guī)則設(shè)定,但是要注意不能重復(fù)
6.5慌烧、分表數(shù)的確定
一般單表數(shù)據(jù)量控制在100W到5000W之間比較合理逐抑,可以根據(jù)預(yù)估未來2-3年數(shù)據(jù)的增長預(yù)期計算出合適的分表數(shù)
6.6、歷史訂單處理
歷史訂單沒有按照訂單號生成規(guī)則來生成屹蚊,但是歷史訂單也會有買家id厕氨,所以拆分函數(shù)配置時,特意將買家id后N位作為前置條件汹粤,歷史訂單會以買家id后N位作為分表鍵落庫命斧。
給歷史訂單建一個索引表,只保留訂單號和買家id關(guān)系
CREATE TABLE `order_history_index` (
`orderId` bigint(20) unsigned NOT NULL COMMENT '訂單ID',
`buyerUserId` bigint(20) unsigned NOT NULL COMMENT '買家的userId'
PRIMARY KEY (`orderId`)
)
歷史訂單根據(jù)訂單號查詢時先過索引表取到買家id嘱兼,再查買家表取到具體數(shù)據(jù)国葬。
注意:
- 還可以在訂單號里面添加了賣家id末2位、買家id末2位芹壕。
- 而分庫汇四、分表的規(guī)則可以設(shè)計成,買家?guī)焓前凑召u家id末尾2位數(shù)分踢涌,賣家?guī)焓前凑召u家id末尾兩位分通孽。
- 所以,只要從訂單號里面拿到了這些數(shù)字信息斯嚎,就知道在哪個庫利虫,哪個表了。
這種辦法堡僻,與微信的紅包訂單號是類似的糠惫,末尾三位數(shù)包含了庫信息、表信息钉疫。
- 如果是按照用戶id的后4位數(shù)取模分散訂單數(shù)據(jù)硼讽。那么訂單號的生成,可以在后面加上用戶id的后4位數(shù)牲阁。
- 那么固阁,雖然是按照用戶id來對訂單表分庫分表的壤躲。其實可以直接根據(jù)訂單號,就知道這個訂單在哪個庫哪個表了备燃。
- 如果是B2B2C系統(tǒng)碉克,涉及到賣家和買家。那么可以把賣家和買家的id后面4位都加進去并齐。不過是不是訂單號有些偏長漏麦。
七、總結(jié)
按照上述方式對訂單分庫分表后况褪,業(yè)務(wù)里常用的查訂單表sql撕贞,通過編碼層面區(qū)分,路由到各個庫的買家表测垛、賣家表里捏膨,有效解決單表數(shù)據(jù)量過大的問題,提升系統(tǒng)穩(wěn)定性和負載能力食侮。
當(dāng)然肯定還有個別復(fù)雜查詢和分表鍵沾不上邊的号涯,這些業(yè)務(wù)場景對實時性要求不高的,可以走ES或者離線庫去查詢
參考:
https://maimai.cn/article/detail?fid=1639183809&efid=I5KVAmjkHZ_UPeF8wcR6gQ
https://www.cnblogs.com/wangtao_20/p/7115962.html