【58沈劍架構系列】細聊冗余表數據一致性
本文主要討論四個問題:
(1)為什麽會有冗余表的需求
(2)如何實現冗余表
(3)正反冗余表誰先執行
(4)冗余表如何保證數據的一致性
一、需求緣起
互聯網很多業務場景的數據量很大,此時數據庫架構要進行水平切分,水平切分會有一個patition key,通過patition key的查詢能夠直接定位到庫,但是非patition key上的查詢可能就需要掃描多個庫了。
例如訂單表,業務上對用戶和商家都有訂單查詢需求:
Order(oid, info_detail)
T(buyer_id, seller_id, oid)
如果用buyer_id來分庫,seller_id的查詢就需要掃描多庫。
如果用seller_id來分庫,buyer_id的查詢就需要掃描多庫。
這類需求,為了做到高吞吐量低延時的查詢,往往使用“數據冗余”的方式來實現,就是文章標題裏說的“冗余表”:
T1(buyer_id, seller_id, oid)
T2(seller_id, buyer_id, oid)
同一個數據,冗余兩份,一份以buyer_id來分庫,滿足買家的查詢需求;
一份以seller_id來分庫,滿足賣家的查詢需求。
二、冗余表的實現方案
【方法一:服務同步寫】
顧名思義,由服務層同步寫冗余數據,如上圖1-4流程:
(1)業務方調用服務,新增數據
(2)服務先插入T1數據
(3)服務再插入T2數據
(4)服務返回業務方新增數據成功
優點:
(1)不復雜,服務層由單次寫,變兩次寫
(2)數據一致性相對較高(因為雙寫成功才返回)
缺點:
(1)請求的處理時間增加(要插入次,時間加倍)
(2)數據仍可能不一致,例如第二步寫入T1完成後服務重啟,則數據不會寫入T2
如果系統對處理時間比較敏感,引出常用的第二種方案
【方法二:服務異步寫】
數據的雙寫並不再由服務來完成,服務層異步發出一個消息,通過消息總線發送給一個專門的數據復制服務來寫入冗余數據,如上圖1-6流程:
(1)業務方調用服務,新增數據
(2)服務先插入T1數據
(3)服務向消息總線發送一個異步消息(發出即可,不用等返回,通常很快就能完成)
(4)服務返回業務方新增數據成功
(5)消息總線將消息投遞給數據同步中心
(6)數據同步中心插入T2數據
優點:
(1)請求處理時間短(只插入1次)
缺點:
(1)系統的復雜性增加了,多引入了一個組件(消息總線)和一個服務(專用的數據復制服務)
(2)因為返回業務線數據插入成功時,數據還不一定插入到T2中,因此數據有一個不一致時間窗口(這個窗口很短,最終是一致的)
(3)在消息總線丟失消息時,冗余表數據會不一致
如果想解除“數據冗余”對系統的耦合,引出常用的第三種方案
【方法三:線下異步寫】
數據的雙寫不再由服務層來完成,而是由線下的一個服務或者任務來完成,如上圖1-6流程:
(1)業務方調用服務,新增數據
(2)服務先插入T1數據
(3)服務返回業務方新增數據成功
(4)數據會被寫入到數據庫的log中
(5)線下服務或者任務讀取數據庫的log
(6)線下服務或者任務插入T2數據
優點:
(1)數據雙寫與業務完全解耦
(2)請求處理時間短(只插入1次)
缺點:
(1)返回業務線數據插入成功時,數據還不一定插入到T2中,因此數據有一個不一致時間窗口(這個窗口很短,最終是一致的)
(2)數據的一致性依賴於線下服務或者任務的可靠性
上述三種方案各有優缺點,但不管哪種方案,都會面臨“究竟先寫T1還是先寫T2”的問題?這該怎麽辦呢?
三、究竟先寫正表還是反表
對於一個不能保證事務性的操作,一定涉及“哪個任務先做,哪個任務後做”的問題,解決這個問題的方向是:
【如果出現不一致】,誰先做對業務的影響較小,就誰先執行。
以上文的訂單生成業務為例,buyer和seller冗余表都需要插入數據:
T1(buyer_id, seller_id, oid)
T2(seller_id, buyer_id, oid)
用戶下單時,如果“先插入buyer表T1,再插入seller冗余表T2”,當第一步成功、第二步失敗時,出現的業務影響是“買家能看到自己的訂單,賣家看不到推送的訂單”
相反,如果“先插入seller表T2,再插入buyer冗余表T1”,當第一步成功、第二步失敗時,出現的業務影響是“賣家能看到推送的訂單,賣家看不到自己的訂單”
由於這個生成訂單的動作是買家發起的,買家如果看不到訂單,會覺得非常奇怪,並且無法支付以推動訂單狀態的流轉,此時即使賣家看到有人下單也是沒有意義的。
因此,在此例中,應該先插入buyer表T1,再插入seller表T2。
however,記住結論:【如果出現不一致】,誰先做對業務的影響較小,就誰先執行。
四、如何保證數據的一致性
從二節和第三節的討論可以看到,不管哪種方案,因為兩步操作不能保證原子性,總有出現數據不一致的可能,那如何解決呢?
【方法一:線下掃面正反冗余表全部數據】
如上圖所示,線下啟動一個離線的掃描工具,不停的比對正表T1和反表T2,如果發現數據不一致,就進行補償修復。
優點:
(1)比較簡單,開發代價小
(2)線上服務無需修改,修復工具與線上服務解耦
缺點:
(1)掃描效率低,會掃描大量的“已經能夠保證一致”的數據
(2)由於掃描的數據量大,掃描一輪的時間比較長,即數據如果不一致,不一致的時間窗口比較長
有沒有只掃描“可能存在不一致可能性”的數據,而不是每次掃描全部數據,以提高效率的優化方法呢?
【方法二:線下掃描增量數據】
每次只掃描增量的日誌數據,就能夠極大提高效率,縮短數據不一致的時間窗口,如上圖1-4流程所示:
(1)寫入正表T1
(2)第一步成功後,寫入日誌log1
(3)寫入反表T2
(4)第二步成功後,寫入日誌log2
當然,我們還是需要一個離線的掃描工具,不停的比對日誌log1和日誌log2,如果發現數據不一致,就進行補償修復
優點:
(1)雖比方法一復雜,但仍然是比較簡單的
(2)數據掃描效率高,只掃描增量數據
缺點:
(1)線上服務略有修改(代價不高,多寫了2條日誌)
(2)雖然比方法一更實時,但時效性還是不高,不一致窗口取決於掃描的周期
有沒有實時檢測一致性並進行修復的方法呢?
【方法三:實時線上“消息對”檢測】
這次不是寫日誌了,而是向消息總線發送消息,如上圖1-4流程所示:
(1)寫入正表T1
(2)第一步成功後,發送消息msg1
(3)寫入反表T2
(4)第二步成功後,發送消息msg2
這次不是需要一個周期掃描的離線工具了,而是一個實時訂閱消息的服務不停的收消息。
假設正常情況下,msg1和msg2的接收時間應該在3s以內,如果檢測服務在收到msg1後沒有收到msg2,就嘗試檢測數據的一致性,不一致時進行補償修復
優點:
(1)效率高
(2)實時性高
缺點:
(1)方案比較復雜,上線引入了消息總線這個組件
(2)線下多了一個訂閱總線的檢測服務
however,技術方案本身就是一個投入產出比的折衷,可以根據業務對一致性的需求程度決定使用哪一種方法。我這邊有過好友數據正反表的業務,使用的就是方法二。
【文章轉載自微信公眾號“架構師之路”】
【58沈劍架構系列】細聊冗余表數據一致性