作者:匙凱明
來源:https://blog.csdn.net/qiansg123/article/details/80124349

一元搶寶系統是京東虛擬新興的一個業務系統,上線以來訂單量一直持續增長。在距離618前兩個月時,京東商城商品虛擬研發部對系統做了整體預估,訂單量快速增長及618大促的到來都將帶來單量劇增,屆時勢必會對數據庫容量和負載造成壓力。

分析結果表明數據庫很可能成爲影響性能的瓶頸,並決定對數據庫底層做分庫分表改造,確保數據水平動態擴展能力,滿足數據容量持續增長的需求,並提高下單效率。

一、業務介紹


京東618實踐:一元搶寶系統的數據庫架構優化


上圖是一元搶寶商品詳情頁,從圖中可以看出,一元搶寶的商品即商品項,其不同於其他京東商品的地方在於:有期次、總人次和剩餘人次的概念;假設一個商品項有100個庫存,則會分100期次售賣,每期次一個售賣的是一個庫存;總人次即設置的每一期搶寶商品價格,假設1000人次,則商品總價是1000元(每人1元);當剩餘人次爲0時,本期搶寶結束,然後按照相應算法產生搶寶者;然後進行下一期搶寶。

通過技術改造,從整體上來說實現三個目標:

  1. 底層路由策略實現;
  2. 歷史數據遷移;
  3. 業務改造。下面詳細介紹本次改造的過程。


二、數據庫容器預估


分庫分表最重要的是要先做容器預估,依據數據量和業務特性估算出容器/庫/表的數量及分庫分表規則。

假設一天100萬訂單,一年則產生3.6億訂單量;假設數據結構是這樣的:訂單表10個字段,一個字段50個字符;一條訂單則需要500字節存儲,那麼3.6億訂單則需要大約170GB存儲空間;假設每臺機器存儲空間爲200GB,則每年增加一臺機器即可滿足容量需求。而實際需求要根據壓測結果來決定;如壓測其他一些指標是否滿足需求,如QPS、響應時間等。

三、底層路由策略選擇及實現


分庫分表路由策略是基礎,影響整個系統架構,後期業務需求是否滿足和支持,使用是否方便都與此有關。路由策略設計合理,上層業務使用會很方便。一元搶寶項目的路由策略適配和實現是在DAO層實現,對上層業務層透明,可不用關心具體實現,並且路由策略不涉及結構上的改動,對上層不會產生影響。

我們知道常見的分表策略有兩種:

  • hash路由


優點:可實現數據分散,熱點分散;

不足:增加數據庫節點時,會影響路由策略,需做數據遷移;

  • 分區路由(增量區間路由)


優點:策略支持動態擴容,理論上可無限擴展;

不足:存在數據熱點問題,新產生的表,讀寫頻率較高;每次查詢需要經過路由策略表。

當然每種策略都不是完美的,只有最適合業務場景的策略纔是好的。該項目採用的是兩種方式的結合。

首先按搶寶項hash分庫,然後按搶寶期區間段分表,如下圖所示:

京東618實踐:一元搶寶系統的數據庫架構優化


期的路由策略表規則如下:

京東618實踐:一元搶寶系統的數據庫架構優化


爲什麼使用這種策略?

搶寶項是業務上層維度,可以理解爲商品,大部分表中都有這個字段;此id生成時是連續的,長期來看,hash分庫後數據是均衡的。搶寶期是搶寶項下的一個維度,如一個項庫存是100,不停售前提下,會生成100期,在售的期次只有一個。

爲什麼選擇期id區間作爲分表路由策略呢,有朋友會認爲也可以選擇訂單id,從路由策略上來說,沒有問題,但一元搶寶項目的業務場景,有根據項id和期id查詢訂單參與紀錄的場景,所以要考慮通過這兩個維度能查到訂單。另外,使用區間作爲分表策略,可以動態擴展,即使每次查詢經過路由表,這點開銷可以忽略,而且都是通過緩存加載。

那以上策略,可以路由的維度有哪些呢?

  1. 通過訂單id路由:訂單號按照一定規則生成,其存儲了庫和表的信息,可以根據訂單號直接定位到相應的庫和表;
  2. 通過搶寶項id和搶寶期id路由:搶寶項hash定位到庫,搶寶期查詢路由策略表定位到表,具體圖示如下:


京東618實踐:一元搶寶系統的數據庫架構優化


四、聚合查詢及聚合數據同步的實現


有分就涉及到聚合查詢,我們如何實現呢?先看如下架構圖:

京東618實踐:一元搶寶系統的數據庫架構優化


上圖是數據層改造後的架構圖,之前是單表主從模式,改造後爲多個分庫、基礎庫。聚合採用了elastic search (以下簡稱ES)。

爲什麼使用它呢,首先,簡單便捷,容易接入;其次,支持動態擴容分片,對業務層透明等。系統中的聚合查詢主要使用了ES,當然我們有很多降級方案,後面會講到。ES不能當作庫來使用,它並不能百分之百保證數據完整性,所以一定要有數據備份,我們使用了聚合表,保存一段時間內的數據,用於降級使用,一旦ES有延遲或集羣不可用,就會降級查詢聚合表。

同步ES我們是怎麼做的呢?我們使用了canal。有的朋友可能說了,爲什麼不在直接在代碼中插入時去同步,可以這樣做,但有兩個問題,一是同步失敗如何處理,如何保證事務,二是與業務代碼強耦合,借用術語,不beautify。使用canal,代碼解耦,不侵入與代碼。

它其實是模擬了數據庫主從複製機制,僞裝爲一個從庫,當數據庫(爲不影響主庫生產,我們監聽的是從庫)binlog有變化時,canal監聽到,通過解析服務解析過濾binlog,把需要的日誌過濾出來。解析後,我們通過發送MQ消息,消息體是表名和主鍵id,不是整條數據,消費端接到變化的表名和id,實時從庫中查詢最新數據,同步到ES、聚合表。

爲什麼通過MQ消息呢?還可以用以上兩點來解釋,一是消息支持失敗重試,存儲失敗後拋異常,等待下次處理,二是系統間解耦。細心的朋友可以看到,一個消息隊列,通過多個消費訂閱(可以理解爲每個消費者的隊列都是鏡像複製的)。這樣做爲了在存儲時不相互影響;如果使用一個訂閱者處理,存儲ES失敗,其他兩個聚合存儲成功,那也要拋異常或其他處理方式,下次消費時,另兩個聚合還要存儲一次。

以上就是我們聚合和同步聚合的設計。查詢時,一部分業務會先查詢緩存,不存在再查詢ES,如果降級,纔會查庫,正常的聚合查詢都不會查到庫。

五、歷史數據遷移


由於我們系統上線時是單庫,分庫是上線幾個月後做的技改,所以數據需要遷移,主要遷移步驟如下:

京東618實踐:一元搶寶系統的數據庫架構優化


前半部分,從掃描到同步到分庫是新代碼,後面canal到同步ES、聚合表都是複用上面邏輯,這樣設計,降低我們整體工作量,並且保證數據遷移完整。

具體遷移細節如下:

京東618實踐:一元搶寶系統的數據庫架構優化


可以看出,主要分爲兩部分,停機前和停機後。停機前是遷移歷史數據,支持重複遷移;停機後,只遷移增量部分,這樣,大大縮短我們的上線時間。停機後只需要遷移很少的數據量。

遷移就涉及到數據校驗,校驗邏輯整體來說比較簡單:

京東618實踐:一元搶寶系統的數據庫架構優化


三個維度分別和基礎庫做對比,如果不同,重新遷移某一天數據。

六、系統關鍵節點降級


這一部分也很重要,我們的降級主要有兩點,一是canal同步延遲降級,一是ES不可用降級。第一種如下:

京東618實踐:一元搶寶系統的數據庫架構優化

如果canal同步延遲,或者從庫掛掉,開啓開關,掃描主庫數據(最近幾小時)直接同步到ES、聚合表;這樣,即使從庫掛掉,也不影響業務數據,這一點很重要,實際業務場景中我們也遇到過。

ES降級,ES不可用時,關閉ES開關,直接查詢聚合表。

京東618實踐:一元搶寶系統的數據庫架構優化


七、總結


一個系統從設計到最終完成,依賴於整個團隊,每個人的想法、不同思路的碰撞和付出;再有前期合理細緻的設計尤爲重要,每個時間點和具體上線步驟和回滾方案做好詳細計劃;另外,就是細緻深入測試,測試環境和線上多輪測試和迴歸,也是正常上線的重要保證。

以上就是京東一元搶寶項目分庫分表的主要思想,希望有同樣想法的朋友可以深入交流,互相提升系統架構。

作者介紹 匙凱明

  • 京東高級開發工程師,在京東負責一元搶寶系統架構和開發工作;多年互聯網經驗,對於系統架構和設計有自己的見解和經驗。

34張架構史上最全技術知識圖譜

程序員專屬手機壁紙來了。。。

相關文章