1. 程式人生 > >GitChat · 架構 | 從訂單中心開始,聊“多KEY”類業務資料庫水平切分架構實踐

GitChat · 架構 | 從訂單中心開始,聊“多KEY”類業務資料庫水平切分架構實踐

前言

本篇講義將以“訂單中心”為例,介紹“多key”類業務,隨著資料量的逐步增大,資料庫效能顯著降低,資料庫水平切分相關的架構實踐。

一、什麼是“多key”類業務

所謂的“多key”,是指一條元資料中,有多個屬性上存在前臺線上查詢需求。

訂單中心業務分析

訂單中心是一個非常常見的“多key”業務,主要提供訂單的查詢與修改的服務,其核心元資料為:
Order(oid, buyer_uid, seller_uid, time, money, detail…);其中:

  1. oid為訂單ID,主鍵
  2. buyer_uid為買家uid
  3. seller_uid為賣家uid
  4. time, money, detail, …等為訂單屬性

資料庫設計上,一般來說在業務初期,單庫單表就能夠搞定這個需求,典型的架構設計為:

  1. order-center:訂單中心服務,對呼叫者提供友好的RPC介面。
  2. order-db:對訂單進行資料儲存。

隨著訂單量的越來越大,資料庫需要進行水平切分,由於存在多個key上的查詢需求,用哪個欄位進行切分,成了需要解決的關鍵技術問題:

  1. 如果用oid來切分,buyer_uidseller_uid上的查詢則需要遍歷多庫。
  2. 如果用buyer_uidseller_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上的查詢需求,就蛻化為一個“多對多”的業務場景,對於“多對多”的業務,水平切分應該使用“資料冗餘法”。

如上圖所示:

  1. 當有訂單生成時,通過buyer_uid分庫,oid中融入分庫基因,寫入DB-buyer庫。
  2. 通過線下非同步的方式,通過binlog+canal,將資料冗餘到DB-seller庫中。
  3. buyer庫通過buyer_uid分庫,seller庫通過seller_uid分庫,前者滿足oid和buyer_uid的查詢需求,後者滿足seller_uid的查詢需求。

資料冗餘的方法有很多種:

  1. 服務同步雙寫。
  2. 服務非同步雙寫。
  3. 線下非同步雙寫(上圖所示,是線下非同步雙寫)。

不管哪種方案,因為兩步操作不能保證原子性,總有出現數據不一致的可能,高吞吐分散式事務是業內尚未解決的難題,此時的架構優化方向,並不是完全保證資料的一致,而是儘早的發現不一致,並修復不一致。

最終一致性,是高吞吐網際網路業務一致性的常用實踐。保證資料最終一致性的方案有三種:

  1. 冗餘資料全量定時掃描。
  2. 冗餘資料增量日誌掃描。
  3. 冗餘資料線上訊息實時檢測。

這些方案細節在“多對多”業務水平拆分的文章裡詳細展開分析過,便不再贅述。

六、oid/buyer_uid/seller_uid同時存在

通過上述分析:

  • 如果沒有seller_uid,“多key”業務會蛻化為“1對多”業務,此時應該使用“基因法”分庫:使用buyer_uid分庫,在oid中加入分庫基因
  • 如果沒有oid,“多key”業務會蛻化為“多對多”業務,此時應該使用“資料冗餘法”分庫:使用buyer_uidseller_uid來分別分庫,冗餘資料,滿足不同屬性上的查詢需求
  • 如果oid/buyer_uid/seller_uid同時存在,可以使用上述兩種方案的綜合方案,來解決“多key”業務的資料庫水平切分難題。

七、總結

複雜難題的解決,都是一個化繁為簡,逐步擊破的過程。

對於像訂單中心一樣複雜的“多key”類業務,在資料量較大,需要對資料庫進行水平切分時,對於後臺需求,採用“前臺與後臺分離”的架構設計方法

  • 前臺、後臺系統web/service/db分離解耦,避免後臺低效查詢引發前臺查詢抖動。
  • 採用前臺與後臺資料冗餘的設計方式,分別滿足兩側的需求。
  • 採用“外接索引”(例如ES搜尋系統)或者“大資料處理”(例如HIVE)來滿足後臺變態的查詢需求。

對於前臺需求,化繁為簡的設計思路,將“多key”類業務,分解為“1對多”類業務和“多對多”類業務分別解決:

  • 使用“基因法”,解決“1對多”分庫需求:使用buyer_uid分庫,在oid中加入分庫基因,同時滿足oid和buyer_uid上的查詢需求
  • 使用“資料冗餘法”,解決“多對多”分庫需求:使用buyer_uidseller_uid來分別分庫,冗餘資料,滿足buyer_uidseller_uid上的查詢需求
  • 如果oid/buyer_uid/seller_uid同時存在,可以使用上述兩種方案的綜合方案,來解決“多key”業務的資料庫水平切分難題

資料冗餘會帶來一致性問題,高吞吐網際網路業務,要想完全保證事務一致性很難,常見的實踐是最終一致性。

任何脫離業務的架構設計都是耍流氓,共勉。

水平切分是一個很有意思的話題,感謝堅持半年訂閱的小夥伴們,下個月最後一章,為答謝大夥的支援,可免費訂閱(gitchat不允許免費,設定為1元),歡迎大家訂閱。

實錄

https://segmentfault.com/a/1190000010993054