大眾點評訂單系統分庫分表實踐

JulianneNau 8年前發布 | 18K 次閱讀 數據庫 軟件架構

背景

原大眾點評的訂單單表早就已經突破兩百G,由于查詢維度較多,即使加了兩個從庫,優化索引,仍然存在很多查詢不理想的情況。去年大量搶購活動的開展,使數據庫達到瓶頸,應用只能通過限速、異步隊列等對其進行保護;業務需求層出不窮,原有的訂單模型很難滿足業務需求,但是基于原訂單表的DDL又非常吃力,無法達到業務要求。隨著這些問題越來越突出,訂單數據庫的切分就愈發急迫了。

這次切分,我們的目標是未來十年內不需要擔心訂單容量的問題。

垂直切分

先對訂單庫進行垂直切分,將原有的訂單庫分為基礎訂單庫、訂單流程庫等,本文就不展開講了。

水平切分

垂直切分緩解了原來單集群的壓力,但是在搶購時依然捉襟見肘。原有的訂單模型已經無法滿足業務需求,于是我們設計了一套新的統一訂單模型,為同時滿足C端用戶、B端商戶、客服、運營等的需求,我們分別通過用戶ID和商戶ID進行切分,并通過PUMA(我們內部開發的MySQL binlog實時解析服務)同步到一個運營庫。

切分策略

1. 查詢切分

將ID和庫的Mapping關系記錄在一個單獨的庫中。

優點:ID和庫的Mapping算法可以隨意更改。

缺點:引入額外的單點。

2. 范圍切分

比如按照時間區間或ID區間來切分。

優點:單表大小可控,天然水平擴展。

缺點:無法解決集中寫入瓶頸的問題。

3. Hash切分

一般采用Mod來切分,下面著重講一下Mod的策略。

數據水平切分后我們希望是一勞永逸或者是易于水平擴展的,所以推薦采用mod 2^n這種一致性Hash。

以統一訂單庫為例,我們分庫分表的方案是32*32的,即通過UserId后四位mod 32分到32個庫中,同時再將UserId后四位Div 32 Mod 32將每個庫分為32個表,共計分為1024張表。線上部署情況為8個集群(主從),每個集群4個庫。

為什么說這種方式是易于水平擴展的呢?我們分析如下兩個場景。

場景一:數據庫性能達到瓶頸

方法一

按照現有規則不變,可以直接擴展到32個數據庫集群。

方法二

如果32個集群也無法滿足需求,那么將分庫分表規則調整為(32*2^n)*(32/2^n),可以達到最多1024個集群。

場景二:單表容量達到瓶頸(或者1024已經無法滿足你)

方法:

假如單表都已突破200G,200*1024=200T(按照現有的訂單模型算了算,大概一萬千億訂單,相信這一天,嗯,指日可待!),沒關系,32*(32*2^n),這時分庫規則不變,單庫里的表再進行裂變,當然,在目前訂單這種規則下(用userId后四位 mod)還是有極限的,因為只有四位,所以最多拆8192個表,至于為什么只取后四位,后面會有篇幅講到。

另外一個維度是通過ShopID進行切分,規則8*8和UserID比較類似,就不再贅述,需要注意的是Shop庫我們僅存儲了訂單主表,用來滿足Shop維度的查詢。

唯一ID方案

這個方案也很多,主流的有那么幾種:

1. 利用數據庫自增ID

優點:最簡單。

缺點:單點風險、單機性能瓶頸。

2. 利用數據庫集群并設置相應的步長(Flickr方案)

優點:高可用、ID較簡潔。

缺點:需要單獨的數據庫集群。

3. 推ter Snowflake

優點:高性能高可用、易拓展。

缺點:需要獨立的集群以及ZK。

4. 一大波GUID、Random算法

優點:簡單。

缺點:生成ID較長,有重復幾率。

我們的方案

為了減少運營成本并減少額外的風險我們排除了所有需要獨立集群的方案,采用了帶有業務屬性的方案:

時間戳+用戶標識碼+隨機數

有下面幾個好處:

  • 方便、成本低。
  • 基本無重復的可能。
  • 自帶分庫規則,這里的用戶標識碼即為用戶ID的后四位,在查詢的場景下,只需要訂單號就可以匹配到相應的庫表而無需用戶ID,只取四位是希望訂單號盡可能的短一些,并且評估下來四位已經足夠。
  • 可排序,因為時間戳在最前面。

當然也有一些缺點,比如長度稍長,性能要比int/bigint的稍差等。

其他問題

  • 事務支持:我們是將整個訂單領域聚合體切分,維度一致,所以對聚合體的事務是支持的。
  • 復雜查詢:垂直切分后,就跟join說拜拜了;水平切分后,查詢的條件一定要在切分的維度內,比如查詢具體某個用戶下的各位訂單等;禁止不帶切分的維度的查詢,即使中間件可以支持這種查詢,可以在內存中組裝,但是這種需求往往不應該在在線庫查詢,或者可以通過其他方法轉換到切分的維度來實現。

數據遷移

數據庫拆分一般是業務發展到一定規模后的優化和重構,為了支持業務快速上線,很難一開始就分庫分表,垂直拆分還好辦,改改數據源就搞定了,一旦開始水平拆分,數據清洗就是個大問題,為此,我們經歷了以下幾個階段。

第一階段

  • 數據庫雙寫(事務成功以老模型為準),查詢走老模型。
  • 每日job數據對賬(通過DW),并將差異補平。
  • 通過job導歷史數據。

第二階段

  • 歷史數據導入完畢并且數據對賬無誤。
  • 依然是數據庫雙寫,但是事務成功與否以新模型為準,在線查詢切新模型。
  • 每日job數據對賬,將差異補平。

第三階段

  • 老模型不再同步寫入,僅當訂單有終態時才會異步補上。
  • 此階段只有離線數據依然依賴老的模型,并且下游的依賴非常多,待DW改造完就可以完全廢除老模型了。

總結

并非所有表都需要水平拆分,要看增長的類型和速度,水平拆分是大招,拆分后會增加開發的復雜度,不到萬不得已不使用。

在大規模并發的業務上,盡量做到在線查詢和離線查詢隔離,交易查詢和運營/客服查詢隔離。

拆分維度的選擇很重要,要盡可能在解決拆分前問題的基礎上,便于開發。

數據庫沒你想象的那么堅強,需要保護,盡量使用簡單的、良好索引的查詢,這樣數據庫整體可控,也易于長期容量規劃以及水平擴展。

最后感謝一下棒棒的DBA團隊和數據庫中間件團隊對項目的大力協助!

 

來自:http://tech.meituan.com/dianping_order_db_sharding.html

 

 本文由用戶 JulianneNau 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!