訂單中心,如何做到資料庫無限容量

數據庫水平切分是一個很有意思的話題,不同業務類型,數據庫水平切分的方法不同。

本篇將以“訂單中心”為例,介紹“多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”業務的數據庫水平切分難題。

數據冗餘會帶來一致性問題,高吞吐互聯網業務,要想完全保證事務一致性很難,常見的實踐是最終一致性

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

---------------------------------------------------------------


分享到:


相關文章: