GitChat · 架構 | 從訂單中心開始,聊“多KEY”類業務資料庫水平切分架構實踐
前言
本篇講義將以“訂單中心”為例,介紹“多key”類業務,隨著資料量的逐步增大,資料庫效能顯著降低,資料庫水平切分相關的架構實踐。
一、什麼是“多key”類業務
所謂的“多key”,是指一條元資料中,有多個屬性上存在前臺線上查詢需求。
訂單中心業務分析
訂單中心是一個非常常見的“多key”業務,主要提供訂單的查詢與修改的服務,其核心元資料為:
Order(oid, buyer_uid, seller_uid, time, money, detail…);其中:
- oid為訂單ID,主鍵
- buyer_uid為買家uid
- seller_uid為賣家uid
- time, money, detail, …等為訂單屬性
資料庫設計上,一般來說在業務初期,單庫單表就能夠搞定這個需求,典型的架構設計為:
- order-center:訂單中心服務,對呼叫者提供友好的RPC介面。
- order-db:對訂單進行資料儲存。
隨著訂單量的越來越大,資料庫需要進行水平切分,由於存在多個key上的查詢需求,用哪個欄位進行切分,成了需要解決的關鍵技術問題:
- 如果用oid來切分,
buyer_uid
和seller_uid
上的查詢則需要遍歷多庫。 - 如果用
buyer_uid
或seller_uid
來切分,其他屬性上的查詢則需要遍歷多庫。
總之,很難有一個完全之策,在展開技術方案之前,先一起梳理梳理查詢需求。
二、訂單中心屬性查詢需求分析
在進行架構討論之前,先來對業務進行簡要分析,看哪些屬性上有查詢需求。
前臺訪問,最典型的有三類需求:
- 訂單實體查詢:通過oid查詢訂單實體,90%流量屬於這類需求。
- 使用者訂單列表查詢:通過buyer_uid分頁查詢使用者歷史訂單列表,9%流量屬於這類需求。
- 商家訂單列表查詢:通過seller_uid分頁查詢商家歷史訂單列表,1%流量屬於這類需求。
前臺訪問的特點:吞吐量大,服務要求高可用,使用者對訂單的訪問一致性要求高,商家對訂單的訪問一致性要求相對較低,可以接受一定時間的延時。
後臺訪問,根據產品、運營需求,訪問模式各異:
按照時間,架構,商品,詳情來進行查詢。
運營側的查詢基本上是批量分頁的查詢,由於是內部系統,訪問量很低,對可用性的要求不高,對一致性的要求也沒這麼嚴格,允許秒級甚至十秒級別的查詢延時。
這兩類不同的業務需求,應該使用什麼樣的架構方案來解決呢?
三、前臺與後臺分離的架構設計
如果前臺業務和後臺業務公用一批服務和一個數據庫,有可能導致,由於後臺的“少數幾個請求”的“批量查詢”的“低效”訪問,導致資料庫的cpu偶爾瞬時100%,影響前臺正常使用者的訪問(例如,訂單查詢超時)。
前臺與後臺訪問的查詢需求不同,對系統的要求也不一樣,故應該兩者解耦,實施“前臺與後臺分離”的架構設計。
前臺業務架構不變,站點訪問,服務分層,資料庫水平切分。
後臺業務需求則抽取獨立的web/service/db來支援,解除系統之間的耦合,對於“業務複雜”“併發量低”“無需高可用”“能接受一定延時”的後臺業務:
- 可以去掉service層,在運營後臺web層通過dao直接訪問資料層。
- 可以不需要反向代理,不需要叢集冗餘。
- 可以通過MQ或者線下非同步同步資料,犧牲一些資料的實時性。
- 可以使用更契合大量資料允許接受更高延時的“索引外接”或者“HIVE”的設計方案。
解決了後臺業務的訪問需求,問題轉化為,前臺的oid,buyer_uid,seller_uid如何來進行資料庫水平切分呢?
多個維度的查詢較為複雜,對於複雜系統設計,可以逐步簡化。
四、假設沒有seller_uid
訂單中心,假設沒有seller_uid
上的查詢需求,而只有oid和buyer_uid
上的查詢需求,就蛻化為一個“1對多”的業務場景,對於“1對多”的業務,水平切分應該使用“基因法”。
再次回顧一下,什麼是分庫基因?
通過buyer_uid
分庫,假設分為16個庫,採用buyer_uid
%16的方式來進行資料庫路由,所謂的模16,其本質是buyer_uid
的最後4個bit決定這行資料落在哪個庫上,這4個bit,就是分庫基因。
也再次回顧一下,什麼是基因法分庫?
在訂單資料oid生成時,oid末端加入分庫基因,讓同一個buyer_uid
下的所有訂單都含有相同基因,落在同一個分庫上。
如上圖所示,buyer_uid=666的使用者下了一個訂單:
- 使用buyer_uid%16分庫,決定這行資料要插入到哪個庫中。
- 分庫基因是buyer_uid的最後4個bit,即1010。
- 在生成訂單標識oid時,先使用一種分散式ID生成演算法生成前60bit(上圖中綠色部分)。
- 將分庫基因加入到oid的最後4個bit(上圖中粉色部分),拼裝成最終64bit的訂單oid(上圖中藍色部分)。
通過這種方法保證,同一個使用者下的所有訂單oid,都落在同一個庫上,oid的最後4個bit都相同,於是:
- 通過buyer_uid%16能夠定位到庫。
- 通過oid%16也能定位到庫。
五、假設沒有oid
訂單中心,假設沒有oid上的查詢需求,而只有buyer_uid和seller_uid上的查詢需求,就蛻化為一個“多對多”的業務場景,對於“多對多”的業務,水平切分應該使用“資料冗餘法”。
如上圖所示:
- 當有訂單生成時,通過buyer_uid分庫,oid中融入分庫基因,寫入DB-buyer庫。
- 通過線下非同步的方式,通過binlog+canal,將資料冗餘到DB-seller庫中。
- buyer庫通過
buyer_uid
分庫,seller庫通過seller_uid
分庫,前者滿足oid和buyer_uid
的查詢需求,後者滿足seller_uid
的查詢需求。
資料冗餘的方法有很多種:
- 服務同步雙寫。
- 服務非同步雙寫。
- 線下非同步雙寫(上圖所示,是線下非同步雙寫)。
不管哪種方案,因為兩步操作不能保證原子性,總有出現數據不一致的可能,高吞吐分散式事務是業內尚未解決的難題,此時的架構優化方向,並不是完全保證資料的一致,而是儘早的發現不一致,並修復不一致。
最終一致性,是高吞吐網際網路業務一致性的常用實踐。保證資料最終一致性的方案有三種:
- 冗餘資料全量定時掃描。
- 冗餘資料增量日誌掃描。
- 冗餘資料線上訊息實時檢測。
這些方案細節在“多對多”業務水平拆分的文章裡詳細展開分析過,便不再贅述。
六、oid/buyer_uid/seller_uid同時存在
通過上述分析:
- 如果沒有
seller_uid
,“多key”業務會蛻化為“1對多”業務,此時應該使用“基因法”分庫:使用buyer_uid
分庫,在oid中加入分庫基因 - 如果沒有oid,“多key”業務會蛻化為“多對多”業務,此時應該使用“資料冗餘法”分庫:使用
buyer_uid
和seller_uid
來分別分庫,冗餘資料,滿足不同屬性上的查詢需求 - 如果oid/buyer_uid/seller_uid同時存在,可以使用上述兩種方案的綜合方案,來解決“多key”業務的資料庫水平切分難題。
七、總結
複雜難題的解決,都是一個化繁為簡,逐步擊破的過程。
對於像訂單中心一樣複雜的“多key”類業務,在資料量較大,需要對資料庫進行水平切分時,對於後臺需求,採用“前臺與後臺分離”的架構設計方法:
- 前臺、後臺系統web/service/db分離解耦,避免後臺低效查詢引發前臺查詢抖動。
- 採用前臺與後臺資料冗餘的設計方式,分別滿足兩側的需求。
- 採用“外接索引”(例如ES搜尋系統)或者“大資料處理”(例如HIVE)來滿足後臺變態的查詢需求。
對於前臺需求,化繁為簡的設計思路,將“多key”類業務,分解為“1對多”類業務和“多對多”類業務分別解決:
- 使用“基因法”,解決“1對多”分庫需求:使用
buyer_uid
分庫,在oid中加入分庫基因,同時滿足oid和buyer_uid
上的查詢需求 - 使用“資料冗餘法”,解決“多對多”分庫需求:使用
buyer_uid
和seller_uid
來分別分庫,冗餘資料,滿足buyer_uid
和seller_uid
上的查詢需求 - 如果oid/buyer_uid/seller_uid同時存在,可以使用上述兩種方案的綜合方案,來解決“多key”業務的資料庫水平切分難題。
資料冗餘會帶來一致性問題,高吞吐網際網路業務,要想完全保證事務一致性很難,常見的實踐是最終一致性。
任何脫離業務的架構設計都是耍流氓,共勉。
水平切分是一個很有意思的話題,感謝堅持半年訂閱的小夥伴們,下個月最後一章,為答謝大夥的支援,可免費訂閱(gitchat不允許免費,設定為1元),歡迎大家訂閱。
實錄
https://segmentfault.com/a/1190000010993054