本文主要討論四個(gè)問(wèn)題:
(1)為什么會(huì)有冗余表的需求
(2)如何實(shí)現(xiàn)冗余表
(3)正反冗余表誰(shuí)先執(zhí)行
(4)冗余表如何保證數(shù)據(jù)的一致性
需求緣起
互聯(lián)網(wǎng)很多業(yè)務(wù)場(chǎng)景的數(shù)據(jù)量很大流强,此時(shí)數(shù)據(jù)庫(kù)架構(gòu)要進(jìn)行水平切分砂吞,水平切分會(huì)有一個(gè)patition key倦踢,通過(guò)patition key的查詢(xún)能夠直接定位到庫(kù)始绍,但是非patition key上的查詢(xún)可能就需要掃描多個(gè)庫(kù)了抬虽。
例如訂單表脑题,業(yè)務(wù)上對(duì)用戶(hù)和商家都有訂單查詢(xún)需求:
Order(oid, info_detail)
T(buyer_id, seller_id, oid)
如果用buyer_id來(lái)分庫(kù)许师,seller_id的查詢(xún)就需要掃描多庫(kù)。
如果用seller_id來(lái)分庫(kù)涉馅,buyer_id的查詢(xún)就需要掃描多庫(kù)归园。
這類(lèi)需求,為了做到高吞吐量低延時(shí)的查詢(xún)稚矿,往往使用“數(shù)據(jù)冗余”的方式來(lái)實(shí)現(xiàn)庸诱,就是文章標(biāo)題里說(shuō)的“冗余表”:
T1(buyer_id, seller_id, oid)
T2(seller_id, buyer_id, oid)
同一個(gè)數(shù)據(jù)捻浦,冗余兩份,一份以buyer_id來(lái)分庫(kù)桥爽,滿足買(mǎi)家的查詢(xún)需求朱灿;
一份以seller_id來(lái)分庫(kù),滿足賣(mài)家的查詢(xún)需求钠四。
冗余表的實(shí)現(xiàn)方案
方法一:服務(wù)同步寫(xiě)
顧名思義盗扒,由服務(wù)層同步寫(xiě)冗余數(shù)據(jù),如上圖1-4流程:
(1)業(yè)務(wù)方調(diào)用服務(wù)缀去,新增數(shù)據(jù)
(2)服務(wù)先插入T1數(shù)據(jù)
(3)服務(wù)再插入T2數(shù)據(jù)
(4)服務(wù)返回業(yè)務(wù)方新增數(shù)據(jù)成功
優(yōu)點(diǎn):
(1)不復(fù)雜侣灶,服務(wù)層由單次寫(xiě),變兩次寫(xiě)
(2)數(shù)據(jù)一致性相對(duì)較高(因?yàn)殡p寫(xiě)成功才返回)
缺點(diǎn):
(1)請(qǐng)求的處理時(shí)間增加(要插入次缕碎,時(shí)間加倍)
(2)數(shù)據(jù)仍可能不一致褥影,例如第二步寫(xiě)入T1完成后服務(wù)重啟,則數(shù)據(jù)不會(huì)寫(xiě)入T2
方法二:服務(wù)異步寫(xiě)
數(shù)據(jù)的雙寫(xiě)并不再由服務(wù)來(lái)完成咏雌,服務(wù)層異步發(fā)出一個(gè)消息凡怎,通過(guò)消息總線發(fā)送給一個(gè)專(zhuān)門(mén)的數(shù)據(jù)復(fù)制服務(wù)來(lái)寫(xiě)入冗余數(shù)據(jù),如上圖1-6流程:
(1)業(yè)務(wù)方調(diào)用服務(wù)赊抖,新增數(shù)據(jù)
(2)服務(wù)先插入T1數(shù)據(jù)
(3)服務(wù)向消息總線發(fā)送一個(gè)異步消息(發(fā)出即可统倒,不用等返回,通常很快就能完成)
(4)服務(wù)返回業(yè)務(wù)方新增數(shù)據(jù)成功
(5)消息總線將消息投遞給數(shù)據(jù)同步中心
(6)數(shù)據(jù)同步中心插入T2數(shù)據(jù)
優(yōu)點(diǎn):
(1)請(qǐng)求處理時(shí)間短(只插入1次)
缺點(diǎn):
(1)系統(tǒng)的復(fù)雜性增加了氛雪,多引入了一個(gè)組件(消息總線)和一個(gè)服務(wù)(專(zhuān)用的數(shù)據(jù)復(fù)制服務(wù))
(2)因?yàn)榉祷貥I(yè)務(wù)線數(shù)據(jù)插入成功時(shí)房匆,數(shù)據(jù)還不一定插入到T2中,因此數(shù)據(jù)有一個(gè)不一致時(shí)間窗口(這個(gè)窗口很短报亩,最終是一致的)
(3)在消息總線丟失消息時(shí)坛缕,冗余表數(shù)據(jù)會(huì)不一致
方法三:線下異步寫(xiě)
數(shù)據(jù)的雙寫(xiě)不再由服務(wù)層來(lái)完成,而是由線下的一個(gè)服務(wù)或者任務(wù)來(lái)完成捆昏,如上圖1-6流程:
(1)業(yè)務(wù)方調(diào)用服務(wù),新增數(shù)據(jù)
(2)服務(wù)先插入T1數(shù)據(jù)
(3)服務(wù)返回業(yè)務(wù)方新增數(shù)據(jù)成功
(4)數(shù)據(jù)會(huì)被寫(xiě)入到數(shù)據(jù)庫(kù)的log中
(5)線下服務(wù)或者任務(wù)讀取數(shù)據(jù)庫(kù)的log
(6)線下服務(wù)或者任務(wù)插入T2數(shù)據(jù)
優(yōu)點(diǎn):
(1)數(shù)據(jù)雙寫(xiě)與業(yè)務(wù)完全解耦
(2)請(qǐng)求處理時(shí)間短(只插入1次)
缺點(diǎn):
(1)返回業(yè)務(wù)線數(shù)據(jù)插入成功時(shí)毙沾,數(shù)據(jù)還不一定插入到T2中骗卜,因此數(shù)據(jù)有一個(gè)不一致時(shí)間窗口(這個(gè)窗口很短,最終是一致的)
(2)數(shù)據(jù)的一致性依賴(lài)于線下服務(wù)或者任務(wù)的可靠性
上述三種方案各有優(yōu)缺點(diǎn)左胞,但不管哪種方案寇仓,都會(huì)面臨“究竟先寫(xiě)T1還是先寫(xiě)T2”的問(wèn)題?這該怎么辦呢烤宙?
究竟先寫(xiě)正表還是反表
對(duì)于一個(gè)不能保證事務(wù)性的操作遍烦,一定涉及“哪個(gè)任務(wù)先做,哪個(gè)任務(wù)后做”的問(wèn)題躺枕,解決這個(gè)問(wèn)題的方向是:
如果出現(xiàn)不一致服猪,誰(shuí)先做對(duì)業(yè)務(wù)的影響較小供填,就誰(shuí)先執(zhí)行。
以上文的訂單生成業(yè)務(wù)為例罢猪,buyer和seller冗余表都需要插入數(shù)據(jù):
T1(buyer_id, seller_id, oid)
T2(seller_id, buyer_id, oid)
用戶(hù)下單時(shí)近她,如果“先插入buyer表T1,再插入seller冗余表T2”膳帕,當(dāng)?shù)谝徊匠晒φ成印⒌诙绞r(shí),出現(xiàn)的業(yè)務(wù)影響是“買(mǎi)家能看到自己的訂單危彩,賣(mài)家看不到推送的訂單”
相反攒磨,如果“先插入seller表T2,再插入buyer冗余表T1”汤徽,當(dāng)?shù)谝徊匠晒γ溏帧⒌诙绞r(shí),出現(xiàn)的業(yè)務(wù)影響是“賣(mài)家能看到推送的訂單泻骤,賣(mài)家看不到自己的訂單”
由于這個(gè)生成訂單的動(dòng)作是買(mǎi)家發(fā)起的漆羔,買(mǎi)家如果看不到訂單,會(huì)覺(jué)得非常奇怪狱掂,并且無(wú)法支付以推動(dòng)訂單狀態(tài)的流轉(zhuǎn)演痒,此時(shí)即使賣(mài)家看到有人下單也是沒(méi)有意義的。
因此趋惨,在此例中鸟顺,應(yīng)該先插入buyer表T1,再插入seller表T2器虾。
如何保證數(shù)據(jù)的一致性
方法一:線下掃描增量數(shù)據(jù)
每次只掃描增量的日志數(shù)據(jù)讯嫂,就能夠極大提高效率,縮短數(shù)據(jù)不一致的時(shí)間窗口兆沙,如上圖1-4流程所示:
(1)寫(xiě)入正表T1
(2)第一步成功后欧芽,寫(xiě)入日志log1
(3)寫(xiě)入反表T2
(4)第二步成功后,寫(xiě)入日志log2
當(dāng)然葛圃,我們需要一個(gè)離線的掃描工具千扔,不停的比對(duì)日志log1和日志log2,如果發(fā)現(xiàn)數(shù)據(jù)不一致库正,就進(jìn)行補(bǔ)償修復(fù)曲楚。
方法二:實(shí)時(shí)線上“消息對(duì)”檢測(cè)
這次不是寫(xiě)日志了,而是向消息總線發(fā)送消息褥符,如上圖1-4流程所示:
(1)寫(xiě)入正表T1
(2)第一步成功后龙誊,發(fā)送消息msg1
(3)寫(xiě)入反表T2
(4)第二步成功后,發(fā)送消息msg2
這次不是需要一個(gè)周期掃描的離線工具了喷楣,而是一個(gè)實(shí)時(shí)訂閱消息的服務(wù)不停的收消息趟大。
假設(shè)正常情況下鹤树,msg1和msg2的接收時(shí)間應(yīng)該在3s以?xún)?nèi),如果檢測(cè)服務(wù)在收到msg1后沒(méi)有收到msg2护昧,就嘗試檢測(cè)數(shù)據(jù)的一致性魂迄,不一致時(shí)進(jìn)行補(bǔ)償修復(fù)