search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

京東分散式資料庫系統演進之路

作者:張成遠,京東雲資深架構師,《Mariadb原理與實現》作者,開源項目speedy作者,2012年加入京東資料庫研發團隊,負責京東分散式資料庫系統的架構與研發工作,主導了京東分散式資料庫系統在公司的落地及大規模推廣,擅長高性能伺服器開發、分散式緩存/資料庫/存儲等大規模分散式系統架構。

關於資料庫的使用,在京東有幾個趨勢,早期在京東主要用SqlServer及Oracle也有少量採用MySQL,隨著業務發展技術積累及使用成本等因素,很多業務都開始使用MySQL,包括早期使用SqlServer及Oracle的很多核心業務也都漸漸的開始遷移到MySQL,單機的MySQL往往無法支撐這類業務,需要考分散式的解決方案,另外原本使用MySQL的業務隨著數據量及訪問量的增加也會遇到瓶頸最終也會考慮採用分散式解決的方案,整個京東發展趨勢如圖1所示。

圖1 業務使用資料庫演變趨勢

分散式的資料庫解決方案有很多種,在各個互聯網公司使用得也是非常的普遍,本質上就是將數據拆開存儲在多個節點上從而緩解單節點的壓力,業務層面也可以根據業務特點自行進行拆分,如圖2所示,假設有一張user表,以ID為拆分鍵,假設拆分成兩份,最簡單的就是奇數ID的數據落到一個存儲節點上,偶數ID的數據落到另外一個存儲節點上,實際部署示意圖如圖3所示。

除了業務層面做拆分,也可以考慮採用較為通用的一些解決方案,主要分為兩類,一類是客戶端解決方案,這種方案是在業務應用中引入特定的客戶端包,通過該客戶端包完成數據的拆分查詢及結果匯總等操作,這種方案對業務有一定侵入性,隨著業務應用實例部署的數量比較大,資料庫端可能會面臨連接數壓力比較大的問題,另外版本升級也比較困難,優點是鏈路較短,從應用實例直接到資料庫。

圖2 數據拆分示意圖

另一類是中間件的解決方案,這種方案是提供兼容資料庫傳輸協議及語法規範的代理,業務在連接中間件的時候可以直接使用傳統的JDBC等客戶端,從而大大減輕了業務開發層面的負擔,弊端是中間件的開發難度會比客戶端方案稍微高一點,另外網路傳輸鏈路上多走了一段,理論上對性能略有影響,實際使用環境中這些系統都是在機房內網訪問,這種網路上的影響完全可以忽略不計。

圖3 系統部署示意圖

根據上述分析,為了更好的支撐京東大量的大規模數據量的業務,我們開發了一套兼容MySQL協議的分散式資料庫的中間件解決方案,我們稱之為JProxy,這套方案經過了多次的演變最終完成並支撐了京東全集團的去Oracle/Sqlserver任務。

JProxy第一個版本如圖4所示,每個JProxy都會有一個配置文件,我們會在配置文件中配置相應業務的庫表拆分信息及路由信息, JProxy接收到SQL以後會對SQL進行解析再根據路由信息決定SQL是否需要重寫及該發往哪些節點,等各節點結果返回以後再將結果匯總按照MySQL傳輸協議返回給應用。

結合上文的例子,當用戶查詢user這張表時假設SQL語句是select * from user where id = 1 or id = 2,當收到這條SQL以後,JProxy會將SQL拆分為select * from user where id=1 及select * from user where id = 2, 再分別把這兩條sql語句發往後端的節點上,最後將兩個節點上獲取到的兩條記錄一併返回給應用。

這種方案在業務庫表比較少的時候是可行的,隨著業務的發展庫表的數量可能會不斷增加,尤其是針對去Oracle的業務在切換資料庫的時候可能是一次切換幾張表,下一次再切換另外幾張表,這就要求經常修改配置文件。另外JProxy在部署的時候至少需要部署兩份甚至多份,如圖5所示,此時面臨一個問題是如何保證所有的配置文件在不斷修改的過程中是完全一致的。在早期運維過程中,我們靠人工修改完一份配置文件,再將相應的配置文件拷貝給其他的JProxy,確保JProxy配置文件內容一致,這個過程心智負擔較重且容易出錯。

圖4 版本一

圖5 配置文件

在之後的版本中我們引入了JManager模塊,這個模塊負責的工作是管理配置文件中的路由元信息,如圖6所示。JProxy的路由元信息都是通過JManager來統一獲取,我們只需要通過JManager往元資料庫里添加修改路由元數據,操作完成以後通知各個JProxy動態載入路由信息就可以保證每個JProxy的路由信息是完全一致的,從而解決維護路由元信息一致性的痛點。

圖6 版本二

在提到分散式資料庫解決方案時一定會考慮的一個問題是擴容問題,擴容有兩種方式,一種我們稱之為re-sharding方案,簡單的說就是一片拆兩片,兩片拆為四片,如圖7所示,原本只有一個MySQL實例一個shard,之後拆分成shard1和shard2兩個分片,之後再添加新的MySQL實例,將shard1拆分成shard11和shard12兩個分片,將shard2拆分成shard21和shard22兩個分片放到另外新加的MySQL實例上,這種擴容方式是最理想的,但具體實現的時候會略微麻煩一點,我們短期之內選擇了另一種偏保守一點在合理預估前提下足以支撐業務發展的擴容模式,我們稱之為pre-sharding方案,這種方案是預先拆分在一定時期內足夠用的分片數,在前期數據量較少時這些分片可以放在一個或少量的幾個MySQL實例上,等後期數據量增大以後可以往集群中加新的MySQL實例,將原本的分片遷移到新添加的MySQL實例上,如圖8所示,我們在一開始就拆分成了shard1、shard2、shard3、shard4四個分片,這四個分片最初是在一個MySQL實例上,數據量增大以後我們可以添加新的MySQL實例,將shard3和shard4遷移新的MySQL實例上,整個集群分片數沒有發生變化但是容量已經變成了原來的兩倍。

圖7 re-sharding方案

圖8 pre-sharding方案

Pre-sharding方案相當於通過遷移完成達到擴容的目的,分片位置的變動涉及到數據的遷移驗證及路由元數據的變更等一系列變動,所以我們引入了JTransfer系統,如圖9所示。JTransfer可以做到在線無縫遷移,遷移擴容時只需提交一條遷移計劃,指定將某個分片從哪個源實例遷移到哪個目標實例,可以指定在何時開始遷移任務,等到了時間點系統會自動開始做遷移。整個遷移過程中涉及到遷移基礎全量數據和遷移過程中業務訪問產生的增量數據,一開始會將基礎全量數據從源實例中dump出來到目標實例恢復,確認數據正確以後開始追趕增量數據,當增量數據追趕到一定程度系統預估可以快速追趕結束時,我們會做一個短暫的鎖定操作,從而確保將最後的增量全部追趕完成,這個鎖定時間也是在提交遷移任務時可以指定的一個參數,比如最多只能鎖定20s,如果因為此時訪問量突然增大等原因最終剩餘的增量沒能在20s內追趕完成,整個遷移任務將會放棄,確保對線上訪問影響達到最小。遷移完成之後會將路由元信息進行修改,同時將路由元信息推送給所有的JProxy,最後再解除鎖定,訪問將根據路由打到分片所在的新位置。

圖9 版本三

系統在生產環境中使用的時候,除了考慮以上的介紹以外還需要考慮很多部署及運維的事情,首先要考慮的就是系統如何活下來,需要考慮系統的自我保護能力,要確保系統的穩定性,要做到性能能夠滿足業務需求。

在JProxy內部我們採用了基於事件驅動的網路IO模型同時考慮到多核等特點,將整個系統的性能發揮到極致,在壓測時JProxy表現出來的性能隨著MySQL實例的增加幾乎是呈現線性增長的趨勢,而且整個過程中JProxy所在機器毫無壓力。

保證性能還不夠,還需要考慮控制連接數、控制系統內存等,連接數主要是控制連接的數量這個比較好理解,控制內存主要是指控制系統在使用過程中對內存的需求量,比如在做數據抽數時候,sql語句是類似select * from table這種的全量查詢,此時後端所有的MySQL數據會通過多條連接併發地往中間件發送數據,從中間件到應用只有一條連接,如果不對內存進行控制就會造成中間件OOM,在具體實現的時候我們通過將數據壓在TCP棧中來控制中間件前後端連接的網路流速從而很好的保證了整個系統的內存是在可控範圍內。

另外還需要考慮許可權,哪些IP可以訪問哪些IP不能訪問都需要可以精確的控制,具體到某一張表還需要控制增刪改查的許可權,我們建議業務在寫SQL的時候盡量都帶有拆分欄位保證SQL都可以落在某個分片上從而保證整個訪問是足夠的簡單可控,我們為之提供了精細的許可權控制,可以做到表級別的增刪改查許可權,包括是否要帶有拆分欄位,最大程度做到對SQL的控制,保證業務在測試階段寫出不滿足期望的SQL都能及時發現,大大降低後期線上運行時的風險。

除了基本的穩定性之外,在整個系統全局上還需要考慮到服務高可用方案。JProxy是無狀態的,一個業務在同一個機房內部署至少兩個JProxy且必須跨機架的,保證在同一個機房裡JProxy是高可用的。在另外的機房會部署再部署兩個JProxy,做到跨機房的高可用。除了中間件自身的高可用以外還需要保證資料庫層面的高可用,全鏈路的高可用才是真正的高可用。資料庫層面在同一個機房裡會按照一主一從部署,在備用機房會再部署一個備,如圖10所示。JProxy訪問MySQL時通過域名訪問,如果MySQL的主出異常資料庫會進行相應的主從切換操作,JProxy可以訪問到切換以後新的主,如果整個機房的資料庫異常可以直接將數據的域名切換到備用機房,保證JProxy可以訪問到備用機房的資料庫。業務訪問JProxy時也是通過域名訪問,如果一個機房的JProxy都出現了異常,和資料庫類似直接將JProxy前端的域名切換到備用機房,從而保證業務始終都能正常訪問JProxy。

數據高可靠也是非常關鍵的點,我們會這對資料庫的數據進行定期備份,將備份數據存儲到相應的存儲系統中,從而保證資料庫中的數據即使被刪除依然是可以恢復的。

圖10 部署示意圖

系統在線上運行時候監控報警是極其重要的,監控可以分多個層次,如圖11所示,從主機和操作系統的信息到應用系統的信息到特定系統內部特定的信息的監控等,針對操作系統及主機的監控京東有MJDOS系統可以把系統的內存/cpu/磁/網卡/機器負載等各種信息都納入監控系統,這些操作系統的基礎信息對系統異常的診斷非常關鍵,比如因為網路丟包等引起的服務異常都可以在這個監控系統中及時找到根源。

京東還有統一的監控報警系統UMP,這個監控系統主要是給所有的應用系統服務,所有的應用系統按照一定的規則暴露介面,在UMP系統中註冊以後,UMP系統就可以提供一整套監控報警服務,最基本的比如系統的存活監控以及是否有慢查詢等。

除了這兩個基本的監控系統以外,我們還針對整套中間件系統開發了定製的監控系統JMonitor,之所以開發這套監控系統是因為我們需要採集更多的定製的監控信息,在系統發生異常時能夠第一時間定位問題,舉個例子當業務發現TP99下降時往往伴隨著有慢SQL,應用從發送SQL到收到結果這個過程中經過了JProxy到MySQL又從MySQL經過JProxy再回到應用,這條鏈路上任何一個環節都可能慢,不管是哪個階段耗時,我們需要將這種慢SQL的記錄精細化,精細到各個階段都花了多少時間,做到出現慢SQL時能快速準確的找到問題根源快速解決問題。

另外在配合業務去Oracle/SqlServer時,我們不建議使用跨庫的事務,但是會出現有一種情況,同一個事務里的SQL都是帶有拆分欄位的,每條SQL都是單節點的,同一個事務里有多條這種SQL,結果卻出現這個事務是跨庫的,這種事務我們都會有詳細的記錄,業務方可以直接通過JMonitor找到這種事務從而更好的進一步改進。除了這個以外,在測試環境時候業務系統一開始寫的SQL沒有考慮太多的優化可能會出現比較多的慢SQL,這些慢SQL我們都會統一採集在JMonitor系統上進行分析處理,幫助業務方快速迭代調整SQL語句。

圖11 監控體系

業務在使用這套系統的時候 要盡量出現避免跨庫的SQL,有一個很重要的原因是當出現跨庫SQL的時候會耗費MySQL較多的連接如圖12所示,一條不帶拆分欄位的SQL將會發送到所有的分片上,如果在一個MySQL實例上有64個分片,那一條這樣的SQL就會耗費這個MySQL實例上的64個連接,這個資源消耗是非常可觀的,如果可以控制SQL落在單個分片上可以大大降低MySQL實例上的連接壓力。

圖12 連接數

跨庫的分散式事務要要盡量避免,一個是基於MySQL的分散式資料庫中間件的方案無法保證嚴格的分散式事務語義,另一個即使可以做到嚴格的分散式事務語義支持依然是要盡量避免垮庫事務的,多個跨庫的分散式事務在某個分片上發生死鎖將會造成其他分片上的事務也無法繼續導致直接引起大面積的死鎖,即使是單節點上的事務也要盡量控制事務小一點,降低死鎖發生的概率。

具體的路由策略不同的業務可以特殊對待,以京東分揀中心為例,各個分揀中心的大小差異很大,北京上海等大城市的分揀中心數據量很大其他城市的分揀中心相對會小一點, 針對這種特點我們會給其定製路由策略,做到將大的分揀中心的數據落在特定的性能較好的MySQL實例上,其他小的分揀中心的數據可以按照普通的拆分方式處理。

在JProxy系統層面我們可以支持多租戶模式,但考慮到去Oracle/SqlServer的業務往往都是非常重要且數據量巨大的業務,所以我們的系統都是不同的業務獨立部署一套,在部署層面避免各個業務之間的互相影響。考慮到獨立部署會造成一些資源浪費,我們引入了容器系統,將操作系統資源通過容器的方式進行隔離,從而保證系統資源的充分利用。很多問題沒必要一定要在代碼層面解決,代碼層面解決起來比較麻煩或者不能做到百分之百把控的事情可以通過架構層面來解決,架構層面不好解決的事情可以通過部署的層面來解決,部署層面不好解決的事情可以通過產品層面來解決,解決問題的方式各式各樣,需要從整個系統全局角度來綜合考量,引用鄧公的一句話「不管黑貓白貓,能抓老鼠的就是好貓」,同樣的道理能支撐住業務發展的系統就是好的系統。

另外再簡單討論一下為什麼基於MySQL的分散式資料庫中間件系統無法保證嚴格的分散式事務語義支持。所謂分散式事務語義本質上就是事務的語義,包含了ACID屬性,分別是原子性、一致性、持久性、隔離性。

原子性是指一個事務要麼成功要麼失敗,不能存在中間狀態。持久性是指一個事務一旦提交成功那麼要做到系統崩潰以後再恢復依然是成功的。隔離性是指各個併發事務之間是隔離的,不可見的,在資料庫具體實現上可能會分很多個隔離級別。事務的一致性是指要保證系統要處於一個一致的狀態,比如從A賬戶轉了500元到B賬戶,那麼從整體系統來看系統的總金額是沒有發生變化的,不能出現A的賬戶已經減去500元但是B賬戶卻沒有增加500元的情況。

圖13 可串列化調度

事務在資料庫系統中執行的時候有一個可串列化調度的問題,假設有T1、T2、T3三個事務,那麼這三個事務的執行的效果應該和三個事務串列執行效果一樣,也就是最終效果效果應該是{T1/T2/T3, T1/T3/T2, T2/T1/T3, T2/T3/T1, T3/T1/T2, T3/T2/T1}集合中的一個,當涉及到分散式事務時,每個子事務之間的調度要和全局的分散式事務的調度順序一致才能滿足可串列化調度的要求,如圖13所示,T1/T2/T3的三個分散式事務,在一個庫中的調度順序是T1/T2/T3和全局的調度順序一致,在另一個庫中的調度順序變成了T3/T2/T1,此時站在全局的角度來看就打破了可串列化調度,可串列化調度保證了隔離性的實現,當可串列化調度被打破時自然隔離性也就隨之打破,在基於MySQL的分散式中間件方案實現上,因為同一個分散式事務的各個子事務的事務ID是在各個MySQL上生成的,並沒有提供全局的事務ID來保證各個子事務的調度順序和全局的分散式事務一致,導致隔離性是無法保證的,所以說當前基於MySQL的分散式事務是無法保證嚴格的分散式事務語義支持的。當然隨著MySQL引入GR可以做到CAP理論中的強一致,再加強中間件的相關功能及定製MySQL相關功能也是有可能做到支持嚴格的分散式事務的。



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦