轉(zhuǎn)帖|行業(yè)資訊|編輯:黃竹雯|2017-02-23 14:14:39.000|閱讀 412 次
概述:關(guān)于數(shù)據(jù)庫的使用,在京東有幾個趨勢,早期在京東主要用SqlServer及Oracle也有少量采用MySQL,隨著業(yè)務(wù)發(fā)展技術(shù)積累及使用成本等因素,很多業(yè)務(wù)都開始使用MySQL,單機的MySQL往往無法支撐這類業(yè)務(wù),需要考分布式的解決方案,另外原本使用MySQL的業(yè)務(wù)隨著數(shù)據(jù)量及訪問量的增加也會遇到瓶頸最終也會考慮采用分布式解決的方案。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
關(guān)于數(shù)據(jù)庫的使用,在京東有幾個趨勢,早期在京東主要用SqlServer及Oracle也有少量采用MySQL,隨著業(yè)務(wù)發(fā)展技術(shù)積累及使用成本等因素,很多業(yè)務(wù)都開始使用MySQL,包括早期使用SqlServer及Oracle的很多核心業(yè)務(wù)也都漸漸的開始遷移到MySQL,單機的MySQL往往無法支撐這類業(yè)務(wù),需要考分布式的解決方案,另外原本使用MySQL的業(yè)務(wù)隨著數(shù)據(jù)量及訪問量的增加也會遇到瓶頸最終也會考慮采用分布式解決的方案,整個京東發(fā)展趨勢如圖1所示。
圖1 業(yè)務(wù)使用數(shù)據(jù)庫演變趨勢
分布式的數(shù)據(jù)庫解決方案有很多種,在各個互聯(lián)網(wǎng)公司使用得也是非常的普遍,本質(zhì)上就是將數(shù)據(jù)拆開存儲在多個節(jié)點上從而緩解單節(jié)點的壓力,業(yè)務(wù)層面也可以根據(jù)業(yè)務(wù)特點自行進行拆分,如圖2所示,假設(shè)有一張user表,以ID為拆分鍵,假設(shè)拆分成兩份,最簡單的就是奇數(shù)ID的數(shù)據(jù)落到一個存儲節(jié)點上,偶數(shù)ID的數(shù)據(jù)落到另外一個存儲節(jié)點上,實際部署示意圖如圖3所示。
除了業(yè)務(wù)層面做拆分,也可以考慮采用較為通用的一些解決方案,主要分為兩類,一類是客戶端解決方案,這種方案是在業(yè)務(wù)應(yīng)用中引入特定的客戶端包,通過該客戶端包完成數(shù)據(jù)的拆分查詢及結(jié)果匯總等操作,這種方案對業(yè)務(wù)有一定侵入性,隨著業(yè)務(wù)應(yīng)用實例部署的數(shù)量比較大,數(shù)據(jù)庫端可能會面臨連接數(shù)壓力比較大的問題,另外版本升級也比較困難,優(yōu)點是鏈路較短,從應(yīng)用實例直接到數(shù)據(jù)庫。
圖2 數(shù)據(jù)拆分示意圖
另一類是中間件的解決方案,這種方案是提供兼容數(shù)據(jù)庫傳輸協(xié)議及語法規(guī)范的代理,業(yè)務(wù)在連接中間件的時候可以直接使用傳統(tǒng)的JDBC等客戶端,從而大大減輕了業(yè)務(wù)開發(fā)層面的負(fù)擔(dān),弊端是中間件的開發(fā)難度會比客戶端方案稍微高一點,另外網(wǎng)絡(luò)傳輸鏈路上多走了一段,理論上對性能略有影響,實際使用環(huán)境中這些系統(tǒng)都是在機房內(nèi)網(wǎng)訪問,這種網(wǎng)絡(luò)上的影響完全可以忽略不計。
圖3 系統(tǒng)部署示意圖
根據(jù)上述分析,為了更好的支撐京東大量的大規(guī)模數(shù)據(jù)量的業(yè)務(wù),我們開發(fā)了一套兼容MySQL協(xié)議的分布式數(shù)據(jù)庫的中間件解決方案,我們稱之為JProxy,這套方案經(jīng)過了多次的演變最終完成并支撐了京東全集團的去Oracle/Sqlserver任務(wù)。
JProxy第一個版本如圖4所示,每個JProxy都會有一個配置文件,我們會在配置文件中配置相應(yīng)業(yè)務(wù)的庫表拆分信息及路由信息, JProxy接收到SQL以后會對SQL進行解析再根據(jù)路由信息決定SQL是否需要重寫及該發(fā)往哪些節(jié)點,等各節(jié)點結(jié)果返回以后再將結(jié)果匯總按照MySQL傳輸協(xié)議返回給應(yīng)用。
結(jié)合上文的例子,當(dāng)用戶查詢user這張表時假設(shè)SQL語句是select * from user where id = 1 or id = 2,當(dāng)收到這條SQL以后,JProxy會將SQL拆分為select * from user where id=1 及select * from user where id = 2, 再分別把這兩條sql語句發(fā)往后端的節(jié)點上,最后將兩個節(jié)點上獲取到的兩條記錄一并返回給應(yīng)用。
這種方案在業(yè)務(wù)庫表比較少的時候是可行的,隨著業(yè)務(wù)的發(fā)展庫表的數(shù)量可能會不斷增加,尤其是針對去Oracle的業(yè)務(wù)在切換數(shù)據(jù)庫的時候可能是一次切換幾張表,下一次再切換另外幾張表,這就要求經(jīng)常修改配置文件。另外JProxy在部署的時候至少需要部署兩份甚至多份,如圖5所示,此時面臨一個問題是如何保證所有的配置文件在不斷修改的過程中是完全一致的。在早期運維過程中,我們靠人工修改完一份配置文件,再將相應(yīng)的配置文件拷貝給其他的JProxy,確保JProxy配置文件內(nèi)容一致,這個過程心智負(fù)擔(dān)較重且容易出錯。
圖4 版本一
圖5 配置文件
在之后的版本中我們引入了JManager模塊,這個模塊負(fù)責(zé)的工作是管理配置文件中的路由元信息,如圖6所示。JProxy的路由元信息都是通過JManager來統(tǒng)一獲取,我們只需要通過JManager往元數(shù)據(jù)庫里添加修改路由元數(shù)據(jù),操作完成以后通知各個JProxy動態(tài)加載路由信息就可以保證每個JProxy的路由信息是完全一致的,從而解決維護路由元信息一致性的痛點。
圖6 版本二
在提到分布式數(shù)據(jù)庫解決方案時一定會考慮的一個問題是擴容問題,擴容有兩種方式,一種我們稱之為re-sharding方案,簡單的說就是一片拆兩片,兩片拆為四片,如圖7所示,原本只有一個MySQL實例一個shard,之后拆分成shard1和shard2兩個分片,之后再添加新的MySQL實例,將shard1拆分成shard11和shard12兩個分片,將shard2拆分成shard21和shard22兩個分片放到另外新加的MySQL實例上,這種擴容方式是最理想的,但具體實現(xiàn)的時候會略微麻煩一點,我們短期之內(nèi)選擇了另一種偏保守一點在合理預(yù)估前提下足以支撐業(yè)務(wù)發(fā)展的擴容模式,我們稱之為pre-sharding方案,這種方案是預(yù)先拆分在一定時期內(nèi)足夠用的分片數(shù),在前期數(shù)據(jù)量較少時這些分片可以放在一個或少量的幾個MySQL實例上,等后期數(shù)據(jù)量增大以后可以往集群中加新的MySQL實例,將原本的分片遷移到新添加的MySQL實例上,如圖8所示,我們在一開始就拆分成了shard1、shard2、shard3、shard4四個分片,這四個分片最初是在一個MySQL實例上,數(shù)據(jù)量增大以后我們可以添加新的MySQL實例,將shard3和shard4遷移新的MySQL實例上,整個集群分片數(shù)沒有發(fā)生變化但是容量已經(jīng)變成了原來的兩倍。
圖7 re-sharding方案
圖8 pre-sharding方案
Pre-sharding方案相當(dāng)于通過遷移完成達(dá)到擴容的目的,分片位置的變動涉及到數(shù)據(jù)的遷移驗證及路由元數(shù)據(jù)的變更等一系列變動,所以我們引入了JTransfer系統(tǒng),如圖9所示。JTransfer可以做到在線無縫遷移,遷移擴容時只需提交一條遷移計劃,指定將某個分片從哪個源實例遷移到哪個目標(biāo)實例,可以指定在何時開始遷移任務(wù),等到了時間點系統(tǒng)會自動開始做遷移。整個遷移過程中涉及到遷移基礎(chǔ)全量數(shù)據(jù)和遷移過程中業(yè)務(wù)訪問產(chǎn)生的增量數(shù)據(jù),一開始會將基礎(chǔ)全量數(shù)據(jù)從源實例中dump出來到目標(biāo)實例恢復(fù),確認(rèn)數(shù)據(jù)正確以后開始追趕增量數(shù)據(jù),當(dāng)增量數(shù)據(jù)追趕到一定程度系統(tǒng)預(yù)估可以快速追趕結(jié)束時,我們會做一個短暫的鎖定操作,從而確保將最后的增量全部追趕完成,這個鎖定時間也是在提交遷移任務(wù)時可以指定的一個參數(shù),比如最多只能鎖定20s,如果因為此時訪問量突然增大等原因最終剩余的增量沒能在20s內(nèi)追趕完成,整個遷移任務(wù)將會放棄,確保對線上訪問影響達(dá)到最小。遷移完成之后會將路由元信息進行修改,同時將路由元信息推送給所有的JProxy,最后再解除鎖定,訪問將根據(jù)路由打到分片所在的新位置。
圖9 版本三
系統(tǒng)在生產(chǎn)環(huán)境中使用的時候,除了考慮以上的介紹以外還需要考慮很多部署及運維的事情,首先要考慮的就是系統(tǒng)如何活下來,需要考慮系統(tǒng)的自我保護能力,要確保系統(tǒng)的穩(wěn)定性,要做到性能能夠滿足業(yè)務(wù)需求。
在JProxy內(nèi)部我們采用了基于事件驅(qū)動的網(wǎng)絡(luò)IO模型同時考慮到多核等特點,將整個系統(tǒng)的性能發(fā)揮到極致,在壓測時JProxy表現(xiàn)出來的性能隨著MySQL實例的增加幾乎是呈現(xiàn)線性增長的趨勢,而且整個過程中JProxy所在機器毫無壓力。
保證性能還不夠,還需要考慮控制連接數(shù)、控制系統(tǒng)內(nèi)存等,連接數(shù)主要是控制連接的數(shù)量這個比較好理解,控制內(nèi)存主要是指控制系統(tǒng)在使用過程中對內(nèi)存的需求量,比如在做數(shù)據(jù)抽數(shù)時候,sql語句是類似select * from table這種的全量查詢,此時后端所有的MySQL數(shù)據(jù)會通過多條連接并發(fā)地往中間件發(fā)送數(shù)據(jù),從中間件到應(yīng)用只有一條連接,如果不對內(nèi)存進行控制就會造成中間件OOM,在具體實現(xiàn)的時候我們通過將數(shù)據(jù)壓在TCP棧中來控制中間件前后端連接的網(wǎng)絡(luò)流速從而很好的保證了整個系統(tǒng)的內(nèi)存是在可控范圍內(nèi)。
另外還需要考慮權(quán)限,哪些IP可以訪問哪些IP不能訪問都需要可以精確的控制,具體到某一張表還需要控制增刪改查的權(quán)限,我們建議業(yè)務(wù)在寫SQL的時候盡量都帶有拆分字段保證SQL都可以落在某個分片上從而保證整個訪問是足夠的簡單可控,我們?yōu)橹峁┝司?xì)的權(quán)限控制,可以做到表級別的增刪改查權(quán)限,包括是否要帶有拆分字段,最大程度做到對SQL的控制,保證業(yè)務(wù)在測試階段寫出不滿足期望的SQL都能及時發(fā)現(xiàn),大大降低后期線上運行時的風(fēng)險。
除了基本的穩(wěn)定性之外,在整個系統(tǒng)全局上還需要考慮到服務(wù)高可用方案。JProxy是無狀態(tài)的,一個業(yè)務(wù)在同一個機房內(nèi)部署至少兩個JProxy且必須跨機架的,保證在同一個機房里JProxy是高可用的。在另外的機房會部署再部署兩個JProxy,做到跨機房的高可用。除了中間件自身的高可用以外還需要保證數(shù)據(jù)庫層面的高可用,全鏈路的高可用才是真正的高可用。數(shù)據(jù)庫層面在同一個機房里會按照一主一從部署,在備用機房會再部署一個備,如圖10所示。JProxy訪問MySQL時通過域名訪問,如果MySQL的主出異常數(shù)據(jù)庫會進行相應(yīng)的主從切換操作,JProxy可以訪問到切換以后新的主,如果整個機房的數(shù)據(jù)庫異常可以直接將數(shù)據(jù)的域名切換到備用機房,保證JProxy可以訪問到備用機房的數(shù)據(jù)庫。業(yè)務(wù)訪問JProxy時也是通過域名訪問,如果一個機房的JProxy都出現(xiàn)了異常,和數(shù)據(jù)庫類似直接將JProxy前端的域名切換到備用機房,從而保證業(yè)務(wù)始終都能正常訪問JProxy。
數(shù)據(jù)高可靠也是非常關(guān)鍵的點,我們會這對數(shù)據(jù)庫的數(shù)據(jù)進行定期備份,將備份數(shù)據(jù)存儲到相應(yīng)的存儲系統(tǒng)中,從而保證數(shù)據(jù)庫中的數(shù)據(jù)即使被刪除依然是可以恢復(fù)的。
圖10 部署示意圖
系統(tǒng)在線上運行時候監(jiān)控報警是極其重要的,監(jiān)控可以分多個層次,如圖11所示,從主機和操作系統(tǒng)的信息到應(yīng)用系統(tǒng)的信息到特定系統(tǒng)內(nèi)部特定的信息的監(jiān)控等,針對操作系統(tǒng)及主機的監(jiān)控京東有MJDOS系統(tǒng)可以把系統(tǒng)的內(nèi)存/cpu/磁/網(wǎng)卡/機器負(fù)載等各種信息都納入監(jiān)控系統(tǒng),這些操作系統(tǒng)的基礎(chǔ)信息對系統(tǒng)異常的診斷非常關(guān)鍵,比如因為網(wǎng)絡(luò)丟包等引起的服務(wù)異常都可以在這個監(jiān)控系統(tǒng)中及時找到根源。
京東還有統(tǒng)一的監(jiān)控報警系統(tǒng)UMP,這個監(jiān)控系統(tǒng)主要是給所有的應(yīng)用系統(tǒng)服務(wù),所有的應(yīng)用系統(tǒng)按照一定的規(guī)則暴露接口,在UMP系統(tǒng)中注冊以后,UMP系統(tǒng)就可以提供一整套監(jiān)控報警服務(wù),最基本的比如系統(tǒng)的存活監(jiān)控以及是否有慢查詢等。
除了這兩個基本的監(jiān)控系統(tǒng)以外,我們還針對整套中間件系統(tǒng)開發(fā)了定制的監(jiān)控系統(tǒng)JMonitor,之所以開發(fā)這套監(jiān)控系統(tǒng)是因為我們需要采集更多的定制的監(jiān)控信息,在系統(tǒng)發(fā)生異常時能夠第一時間定位問題,舉個例子當(dāng)業(yè)務(wù)發(fā)現(xiàn)TP99下降時往往伴隨著有慢SQL,應(yīng)用從發(fā)送SQL到收到結(jié)果這個過程中經(jīng)過了JProxy到MySQL又從MySQL經(jīng)過JProxy再回到應(yīng)用,這條鏈路上任何一個環(huán)節(jié)都可能慢,不管是哪個階段耗時,我們需要將這種慢SQL的記錄精細(xì)化,精細(xì)到各個階段都花了多少時間,做到出現(xiàn)慢SQL時能快速準(zhǔn)確的找到問題根源快速解決問題。
另外在配合業(yè)務(wù)去Oracle/SqlServer時,我們不建議使用跨庫的事務(wù),但是會出現(xiàn)有一種情況,同一個事務(wù)里的SQL都是帶有拆分字段的,每條SQL都是單節(jié)點的,同一個事務(wù)里有多條這種SQL,結(jié)果卻出現(xiàn)這個事務(wù)是跨庫的,這種事務(wù)我們都會有詳細(xì)的記錄,業(yè)務(wù)方可以直接通過JMonitor找到這種事務(wù)從而更好的進一步改進。除了這個以外,在測試環(huán)境時候業(yè)務(wù)系統(tǒng)一開始寫的SQL沒有考慮太多的優(yōu)化可能會出現(xiàn)比較多的慢SQL,這些慢SQL我們都會統(tǒng)一采集在JMonitor系統(tǒng)上進行分析處理,幫助業(yè)務(wù)方快速迭代調(diào)整SQL語句。
圖11 監(jiān)控體系
業(yè)務(wù)在使用這套系統(tǒng)的時候 要盡量出現(xiàn)避免跨庫的SQL,有一個很重要的原因是當(dāng)出現(xiàn)跨庫SQL的時候會耗費MySQL較多的連接如圖12所示,一條不帶拆分字段的SQL將會發(fā)送到所有的分片上,如果在一個MySQL實例上有64個分片,那一條這樣的SQL就會耗費這個MySQL實例上的64個連接,這個資源消耗是非常可觀的,如果可以控制SQL落在單個分片上可以大大降低MySQL實例上的連接壓力。
圖12 連接數(shù)
跨庫的分布式事務(wù)要要盡量避免,一個是基于MySQL的分布式數(shù)據(jù)庫中間件的方案無法保證嚴(yán)格的分布式事務(wù)語義,另一個即使可以做到嚴(yán)格的分布式事務(wù)語義支持依然是要盡量避免垮庫事務(wù)的,多個跨庫的分布式事務(wù)在某個分片上發(fā)生死鎖將會造成其他分片上的事務(wù)也無法繼續(xù)導(dǎo)致直接引起大面積的死鎖,即使是單節(jié)點上的事務(wù)也要盡量控制事務(wù)小一點,降低死鎖發(fā)生的概率。
具體的路由策略不同的業(yè)務(wù)可以特殊對待,以京東分揀中心為例,各個分揀中心的大小差異很大,北京上海等大城市的分揀中心數(shù)據(jù)量很大其他城市的分揀中心相對會小一點, 針對這種特點我們會給其定制路由策略,做到將大的分揀中心的數(shù)據(jù)落在特定的性能較好的MySQL實例上,其他小的分揀中心的數(shù)據(jù)可以按照普通的拆分方式處理。
在JProxy系統(tǒng)層面我們可以支持多租戶模式,但考慮到去Oracle/SqlServer的業(yè)務(wù)往往都是非常重要且數(shù)據(jù)量巨大的業(yè)務(wù),所以我們的系統(tǒng)都是不同的業(yè)務(wù)獨立部署一套,在部署層面避免各個業(yè)務(wù)之間的互相影響。考慮到獨立部署會造成一些資源浪費,我們引入了容器系統(tǒng),將操作系統(tǒng)資源通過容器的方式進行隔離,從而保證系統(tǒng)資源的充分利用。很多問題沒必要一定要在代碼層面解決,代碼層面解決起來比較麻煩或者不能做到百分之百把控的事情可以通過架構(gòu)層面來解決,架構(gòu)層面不好解決的事情可以通過部署的層面來解決,部署層面不好解決的事情可以通過產(chǎn)品層面來解決,解決問題的方式各式各樣,需要從整個系統(tǒng)全局角度來綜合考量,引用鄧公的一句話“不管黑貓白貓,能抓老鼠的就是好貓”,同樣的道理能支撐住業(yè)務(wù)發(fā)展的系統(tǒng)就是好的系統(tǒng)。
另外再簡單討論一下為什么基于MySQL的分布式數(shù)據(jù)庫中間件系統(tǒng)無法保證嚴(yán)格的分布式事務(wù)語義支持。所謂分布式事務(wù)語義本質(zhì)上就是事務(wù)的語義,包含了ACID屬性,分別是原子性、一致性、持久性、隔離性。
原子性是指一個事務(wù)要么成功要么失敗,不能存在中間狀態(tài)。持久性是指一個事務(wù)一旦提交成功那么要做到系統(tǒng)崩潰以后再恢復(fù)依然是成功的。隔離性是指各個并發(fā)事務(wù)之間是隔離的,不可見的,在數(shù)據(jù)庫具體實現(xiàn)上可能會分很多個隔離級別。事務(wù)的一致性是指要保證系統(tǒng)要處于一個一致的狀態(tài),比如從A賬戶轉(zhuǎn)了500元到B賬戶,那么從整體系統(tǒng)來看系統(tǒng)的總金額是沒有發(fā)生變化的,不能出現(xiàn)A的賬戶已經(jīng)減去500元但是B賬戶卻沒有增加500元的情況。
圖13 可串行化調(diào)度
事務(wù)在數(shù)據(jù)庫系統(tǒng)中執(zhí)行的時候有一個可串行化調(diào)度的問題,假設(shè)有T1、T2、T3三個事務(wù),那么這三個事務(wù)的執(zhí)行的效果應(yīng)該和三個事務(wù)串行執(zhí)行效果一樣,也就是最終效果效果應(yīng)該是{T1/T2/T3, T1/T3/T2, T2/T1/T3, T2/T3/T1, T3/T1/T2, T3/T2/T1}集合中的一個,當(dāng)涉及到分布式事務(wù)時,每個子事務(wù)之間的調(diào)度要和全局的分布式事務(wù)的調(diào)度順序一致才能滿足可串行化調(diào)度的要求,如圖13所示,T1/T2/T3的三個分布式事務(wù),在一個庫中的調(diào)度順序是T1/T2/T3和全局的調(diào)度順序一致,在另一個庫中的調(diào)度順序變成了T3/T2/T1,此時站在全局的角度來看就打破了可串行化調(diào)度,可串行化調(diào)度保證了隔離性的實現(xiàn),當(dāng)可串行化調(diào)度被打破時自然隔離性也就隨之打破,在基于MySQL的分布式中間件方案實現(xiàn)上,因為同一個分布式事務(wù)的各個子事務(wù)的事務(wù)ID是在各個MySQL上生成的,并沒有提供全局的事務(wù)ID來保證各個子事務(wù)的調(diào)度順序和全局的分布式事務(wù)一致,導(dǎo)致隔離性是無法保證的,所以說當(dāng)前基于MySQL的分布式事務(wù)是無法保證嚴(yán)格的分布式事務(wù)語義支持的。當(dāng)然隨著MySQL引入GR可以做到CAP理論中的強一致,再加強中間件的相關(guān)功能及定制MySQL相關(guān)功能也是有可能做到支持嚴(yán)格的分布式事務(wù)的。
文章轉(zhuǎn)自()
最流行的開源關(guān)系型數(shù)據(jù)庫管理系統(tǒng)MySQL點擊下載>>>
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@ke049m.cn