前言
分庫分表是一個老生常談的問題,但網(wǎng)上的大多數(shù)文章只是概要式講述,一些核心步驟一筆帶過渺杉。 這篇文章給大家講述所有可行性方案的同時,系統(tǒng)性挪钓、完整性闡述其思想內(nèi)核是越。
1. 拆分形式
分庫分表拆分形式分為垂直分庫分表和水平分庫分表,這篇文章將聚焦于水平分庫分表
碌上。
原因在于組織倚评、業(yè)務劃分的之初或者大數(shù)據(jù)量之前,在一些開發(fā)人員素質比較高的公司馏予,庫天梧、表先天的就垂拆分的比較清晰了,剩下只是數(shù)據(jù)字段微調吗蚌。 水平分庫分表是側重于技術的問題腿倚,垂直分庫分表是側重于業(yè)務劃分問題纯出。
注意是側重蚯妇,為啥這么說了,以訂單表為例暂筝,用戶下單是訂單補充信息過長箩言,把它拆分出來,放在其它數(shù)據(jù)庫里焕襟,用訂單號關聯(lián)陨收,這是垂直拆分的一種形式,但顯然對業(yè)務操作沒什么影響鸵赖。
從上我們可以得出务漩,大數(shù)據(jù)量下必然要水平分庫分表,不一定要垂直分庫分表它褪。
2.切分策略
2.1 取模切分(Hash取模)
我們一般會用對某個路由Key的Hash值取模饵骨,使數(shù)據(jù)分散到各個表中更加均衡。
比如原始訂單表信息茫打,我們把它分成4張分表:
優(yōu)點:可以較為均勻的將數(shù)據(jù)切分居触,不會有熱點問題
缺點:擴容時需要重新計算切分后的值,涉及到大量的數(shù)據(jù)遷移
2.2 Range切分
優(yōu)點:單表大小可控老赤,天然水平擴展轮洋。
缺點:無法解決集中寫入瓶頸,有熱點問題抬旺。
2.3 查詢切分
查詢切分的好處弊予,是可以解決Hash取模的水平擴展問題和Range切分的熱點問題。
2.3.1基于查詢配置結合ShardGroup方式解決水平擴展問題和熱點問題
(1)表數(shù)據(jù)演示
總得來看表結構呈現(xiàn)share group->share db->share table的樹形結構
(2) 核心流程
step1:通過id所處的范圍找到group_id
step2:通過id取模和groupid找到db_id
step3:通過id所處的范圍和db_id找到table_id
優(yōu)點:理論上
根據(jù)數(shù)據(jù)實際增長情況开财,調整ID和庫块促、表的Mapping關系荣堰,將hash和range結合起來可以解決熱點和水平擴展問題。
缺點:引入額外的配置表竭翠,降低性能振坚,有漏配、錯配的風險斋扰。
2.4 一致性Hash切分
- 建立一個2^32 hash節(jié)點環(huán)渡八,計算database的hash值(常用:hash(節(jié)點前綴 +(ip+端口))%2^32),并映射到hash環(huán)的具體位置
- 將待切分數(shù)據(jù)取值hash(sharding key) % 2^32传货,映射到hash 環(huán)上屎鳍,并按照規(guī)則從root節(jié)點開始順時針查找
if hash(node_0) %2^32 < hash(sharding key) %2^32 <= hash(node_1) %2^32 ;該數(shù)據(jù)歸屬 node_1(database1)
if hash(node_1) %2^32 < hash(sharding key) %2^32 <= hash(node_2) %2^32 ;該數(shù)據(jù)歸屬 node_2(database2)
if hash(node_2) %2^32 < hash(sharding key) %2^32 <= hash(node_3) %2^32 ;該數(shù)據(jù)歸屬 node_3(database3)
...
- 擴容時遷移數(shù)據(jù)時,如新增節(jié)點node_4(database4) 在node_1和 node_2之間问裕,則需要將data_key落在node_1到 node_4的數(shù)據(jù)從原node_2(database2)遷移到node_4(database4)
優(yōu)點:擴容時只需要遷移小部分的數(shù)據(jù)
缺點:如果節(jié)點不夠分散逮壁,會出現(xiàn)Hash環(huán)的傾斜,導致節(jié)點數(shù)據(jù)不均勻
增加虛擬Hash節(jié)點解決數(shù)據(jù)傾斜問題
基于一致性Hash 增加了虛擬節(jié)點粮宛,這些節(jié)點會映射到真實的物理節(jié)點窥淆,這樣就可以調控節(jié)點數(shù)據(jù)分配
優(yōu)點:減少了Hash環(huán)傾斜帶來的影響
缺點:增加復雜度
3.數(shù)據(jù)遷移
數(shù)據(jù)庫拆分一般是業(yè)務發(fā)展到一定規(guī)模后的優(yōu)化和重構,為了支持業(yè)務快速上線,很難一開始就分庫分表,這時候就需要進行數(shù)據(jù)遷移了僧凰。
3.1同步腳本停機遷移(不建議)
寫個腳本老庫的數(shù)據(jù)寫到新庫中。比如词裤,在系統(tǒng)使用的人數(shù)非常少的時候,凌晨1點鳖宾,掛一個公告說系統(tǒng)要維護升級預計N小時吼砂。個腳本將老庫的數(shù)據(jù)都同步到新庫中。
優(yōu)點:操作簡單
缺點:一但數(shù)據(jù)量大鼎文,加上數(shù)據(jù)核對時間渔肩,遷移時間會超出公告時間。
3.2 雙寫數(shù)據(jù)遷移
3.2.1 在業(yè)務代碼中直接雙寫
Step1.數(shù)據(jù)庫雙寫(事務成功以老模型為準)漂问,查詢走老模型赖瞒。
舊數(shù)據(jù)如何遷移到新庫?
通過job導歷史數(shù)據(jù)寫入到新庫蚤假,同時建立映射關系栏饮。
如何保證新庫里的數(shù)據(jù)是最新且完整的?
我們對老庫的更新操作(增刪改)磷仰,同時也要寫入新庫(雙寫)袍嬉。如果操作的數(shù)據(jù)不存在于新庫的話,需要插入到新庫中。同時每日job數(shù)據(jù)對賬伺通,并將差異數(shù)據(jù)補平箍土。這樣就能保證,咱們新庫里的數(shù)據(jù)是最新且完整的罐监。
不在同一庫如何保證事務吴藻?
只保證舊庫寫入事務,差異數(shù)據(jù)由數(shù)據(jù)對賬服務補償弓柱。
Step2.數(shù)據(jù)庫雙寫沟堡,但是事務成功與否以新模型為準,在線查詢切新模型矢空。
在歷史數(shù)據(jù)導入完畢并且數(shù)據(jù)對賬無誤后航罗,執(zhí)行該步驟。
如何查詢舊訂單屁药?
通過order mapping數(shù)據(jù)找到新庫id粥血,在新庫中查詢。
Step3.老模型不再同步寫入酿箭,在線查詢切新模型复亏。
在查詢無異常后,執(zhí)行該步驟七问。
在業(yè)務代碼中直接雙寫方案
優(yōu)點:平滑遷移
缺點:雙寫影響響應時間蜓耻,減少吞吐量
3.2.1 通過數(shù)據(jù)同步工具雙寫
數(shù)據(jù)同步數(shù)據(jù)同步整體方案見下圖
上述方案中不難發(fā)現(xiàn)茫舶,業(yè)務寫一條數(shù)據(jù)到舊實例的一張表械巡,于是產(chǎn)生了一條binlog;data-sync中間件接到binlog后饶氏,將該記錄寫入到新實例讥耗,于是在新實例也產(chǎn)生了一條binlog;此時data-sync中間件又接到了該binlog......不斷循環(huán)
如何解決雙向同步時的binlog循環(huán)消費問題疹启?
采用數(shù)據(jù)染色方案解決古程,只要能夠標識寫入到數(shù)據(jù)庫中的數(shù)據(jù)使data-sync中間件寫入而非業(yè)務寫入,當下次接收到該binlog數(shù)據(jù)的時候就不需要進行再次消息流轉喊崖。所以data-sync中間件要求挣磨,每個數(shù)據(jù)庫實例創(chuàng)建一個事務表,該事務表tb_transaction只有id荤懂、tablename茁裙、status、create_time节仿、update_time幾個字段晤锥,status默認為0。操作如下:
# 開啟事務,用事務保證一下sql的原子性和一致性
start transaction;
set autocommit = 0;
# 更新事務表status=1矾瘾,標識后面的業(yè)務數(shù)據(jù)開始染色
update tb_transaction set status = 1 where tablename = ${tableName};
# 以下是業(yè)務產(chǎn)生
binloginsert xxx;update xxx;update xxx;
# 更新事務表status=0女轿,標識后面的業(yè)務數(shù)據(jù)失去染色,后面業(yè)務正常繼續(xù)消費
update tb_transaction set status = 0 where tablename = ${tableName};
commit;
數(shù)據(jù)同步工具DTS
4.平滑擴容
4.1 全局增量分區(qū)局部散列擴容方案
對于Range和查詢切分切分策略壕翩,增加數(shù)據(jù)庫實例和映射配置即可
4.2 基于主從同步的擴容方案
核心思想是蛉迹,啟動更多的從服務器(取決于希望擴容的服務器量),當從服務器從主服務器同步完成之后放妈,將所有從服務器升級為主服務器婿禽,然后調整路由規(guī)則,再根據(jù)路由規(guī)則刪除每個主服務器中的冗余數(shù)據(jù)
大猛。
4.3 基于數(shù)據(jù)遷移的擴容方案
上文數(shù)據(jù)遷移中扭倾,通過數(shù)據(jù)同步工具雙寫就是此方法。數(shù)據(jù)遷移的過程中就涉及到了平滑擴容挽绩,我在這里把它拿出來膛壹,是為了大家更好理解它。
4.查詢問題
4.1 數(shù)據(jù)多維度查詢
水平切分后唉堪,查詢的條件一定要在切分的維度內(nèi)模聋,比如查詢具體某個用戶下的各位訂單等
;禁止不帶切分的維度的查詢唠亚,即使中間件可以支持這種查詢链方,可以在內(nèi)存中組裝,但是這種需求往往不應該在在線庫查詢灶搜,或者可以通過其他方法轉換到切分的維度來實現(xiàn)祟蚀。
但是我非要多維度查詢怎么辦?
4.1.1 多維度查詢雙寫
比如割卖,對于訂單表我可以同時以user_id前酿、order_id為sharding key冗余分庫分表數(shù)據(jù)。
優(yōu)點:查詢實時性
缺點:寫入性能有影響鹏溯,數(shù)據(jù)冗余
4.1.2 增加全局二級索引庫
每個全局二級索引庫對應一張分布式索引表罢维,和其他分布式表一樣,按照指定的分區(qū)規(guī)則水平拆分為多張物理表丙挽。
優(yōu)點類似ES 倒排索引肺孵,首先通過value 找到id,在通過id找到完整數(shù)據(jù)颜阐。
優(yōu)點:查詢實時性,相較于數(shù)據(jù)冗余
缺點:寫入性能有影響平窘,數(shù)據(jù)冗余
4.1.3 通過binlog數(shù)據(jù)異構查詢
比如,可以把mysql通過binlog寫入到OLAP數(shù)據(jù)庫瞬浓,比如Elasticsearch初婆、 Clickhouse查詢。
優(yōu)點:讀寫分離,方便支持多維度或者全字段索引
缺點:查詢會有實時性問題磅叛,可以用于非實時場景
4.2 order排序查詢
基于上述分片聚合方式我們清晰的了解到如何可以進行跨分片下降數(shù)據(jù)獲取到內(nèi)存中屑咳,但是通過圖中結果可以清晰的了解到返回的數(shù)據(jù)并不像我們預期的那樣有序,那是因為各個節(jié)點下的所有數(shù)據(jù)都是僅遵循各自節(jié)點的數(shù)據(jù)庫排序而不受其他節(jié)點分片影響。 那么如果我們對數(shù)據(jù)進行分片聚合+排序那么又會是什么樣的場景呢
4.2.1 內(nèi)存排序
todo
4.2.2 流式排序
todo
5.灰度發(fā)布
后續(xù)在補充弊琴。兆龙。。
6.總結
分庫分表要考慮的細節(jié)還是很多的敲董,所有才有了NewSql數(shù)據(jù)庫紫皇,但是上面這些解決方案,還是需要掌握的腋寨。
參考
https://blog.csdn.net/mouzeping123/article/details/120061298
https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html
https://cloud.tencent.com/developer/article/2124399
https://mp.weixin.qq.com/s?https://juejin.cn/post/7085132195190276109
https://bbs.csdn.net/topics/605500717
https://blog.51cto.com/u_11475121/2954464
https://zhuanlan.zhihu.com/p/635219596