背景java
數據庫做爲一個很是基礎的系統,任何一家互聯網公司都會使用,數據庫產品也不少,有Oracle、SQL Server 、MySQL、PostgeSQL、MariaDB等,像SQLServer/Oracle 這類數據庫在初期能夠幫業務搞定不少棘手的事情,咱們能夠花更多的精力在業務自己的發展上,但衆所周知也得交很多錢。react
涉及到錢的事情在公司發展壯大之後老是會回來從新審視這個事情的,在京東早期發展的過程當中確實有一些業務的數據就是直接存在oracle或者sqlserver中。nginx
後來隨着業務的發展以及數據量訪問量的不斷增長及成本等方面的考慮,從長遠考慮須要把這些業務用免費的MySQL來存,但單機的MySQL每每沒法直接抗住這些業務,天然而然的咱們就須要考慮引入分佈式的MySQL解決方案幫助業務去SQLServer/Oracle以及支撐將來的發展。redis
方案選型對比及京東實現方案sql
說到分佈式MySQL的解決方案通常來講解決方案主要就兩種,客戶端的方案或者中間代理的方案,以下圖所示。數據庫
這兩種方案各有各的優缺點:客戶端的方案是指會給業務提供一個專門的客戶端的包,這種方案在實現上會更容易一點,若是公司須要快速出一個相對通用的解決方案,客戶端的方案能夠優先考慮。編程
客戶端方案須要爲不一樣的語言提供不一樣的客戶端的包,這點有所侷限。客戶端方案只須要走一段網絡,理論上性能會更好一點。後端
客戶端方案對業務有侵入,有一些系統部署及實現方面的可能能夠控制得更好,但對業務自己不友好,客戶端包升級等方面比較麻煩。性能優化
中間代理的方案是指採用一個兼容MySQL協議的代理的方式,業務可使用任何語言的MySQL客戶端的包,對業務自己無侵入的,這種方案相對來講是最友好的。網絡
中間代理方案開發難度上來講門檻會更高一點,須要考慮先後端的東西,尤爲是與MySQL端交互時本身解析協議的狀況下會更復雜一些。中間代理方案多走一段TCP,對性能理論上會有一些影響。
上述兩種方案有一個很是重要的因素沒有說起,在實際生產環境中面臨一個很是現實的問題是MySQL能支持的鏈接數是有限的。以MySQL5.5來講假設一個MySQL實例配置1000個鏈接,業務應用實例部署了100個,每一個應用實例的數據庫鏈接池配置20個,採用客戶端方案這個MySQL實例都無法正常工做了。
大多數狀況下並非每一個應用實例的每條鏈接都是活躍的,中間代理的方案能夠很好的解決這個問題,應用實例能夠有不少鏈接打到代理上,代理只須要維護較少的與MySQL的鏈接便可知足需求,代理與MySQL之間的鏈接會被業務打過來的訪問重複使用。
另外關於多走一次TCP對性能的影響,從咱們的實際經驗來看其實能夠忽略不計,業務實例一多優先遇到的是MySQL鏈接數的問題,從這個角度來講中間代理的方案會更優。
咱們採用的就是中間代理的方案,京東的分佈式MySQL方案由不少部分組成,有JManager、 JProxy、 JTransfer、JMonitor、JConsole、MySQL,在實際部署的時候還涉及到LVS以及域名系統等。
JManager是中心管理節點,這個節點負責統一管理系統的元信息,元信息包括路由信息、權限管理信息、資源相關的信息等。
JProxy就是一個兼容MySQL協議的代理,負責把客戶端發送過來的SQL按照路由規則發送到相應的數據庫節點上,再把返回的結果進行合併並返回給客戶端。JProxy在啓動的時候會先去JManager中拉取相關的元信息,並在本身的內存中維護一份,平時使用的時候只用自身內存維護的這一份就能夠了。
JProxy的內部實現原理如圖所示。
JTransfer是在線遷移系統,咱們針對業務的數據進行拆分之後,好比某個MySQL實例上有32個庫,等到業務數據量繼續增大之後在這個實例上就放不下了,咱們就須要往整個集羣中加MySQL實例,將以前的32個庫中一部分遷移到這個新增長的實例上,如何在不停業務的狀況完成數據的在線遷移就是JTransfer這個系統來保證的。
JConsole系統能夠理解爲將多個業務的中心管理節點整合起來的一個後臺管理控制系統,這個系統能夠與每一個JManager交互。在具體使用的時候,業務方須要申請建立庫表、拆分規則、什麼權限、對哪些IP受權,咱們會經過JConsole系統與JManager交互完成元數據的配置。
JMonitor系統會將各個業務的jproxy以及MySQL相關的信息採集起來,整合到一塊兒造成一個統一的監控系統,完成對系統的全面詳盡的監控。
網絡模型
JProxy做爲一個很是典型的代理服務,程序自己的性能很是關鍵,具體在實現的時候咱們參考了Nginx的網絡模型。
你們都知道Nginx的性能很是高,根據機器核數配置相應的worker數就能夠,每一個worker能夠理解爲圍繞一個epoll把先後端的鏈接以徹底基於事件驅動的方式串在一塊兒,避免了上下文切換避免了鎖等待等各類可能阻塞或者耗時的操做。
一樣的網絡模型也能夠參考一下Redis的實現,redis雖然不像nginx須要考慮先後端鏈接的處理,但redis的模型也是一種很是相似的經典的實現方式。
JProxy整個網絡模型如圖所示,採用一個全局的nioacceptor以及多個nioreactor,由nioacceptor統一accept鏈接,以後把鏈接分給某個nioreactor。
nioreactor能夠理解爲底層就是一個epoll(java nio實現),先後端的鏈接都是註冊在這個epoll上,咱們只須要根據事件是讀事件或寫事件調用相應的回調函數便可。這種模型的特色是系統幾乎沒有太多的上下文切換,並且性能很高。
基於事件驅動的網絡模型的好處是性能很高,但問題也很明顯,編寫時複雜度很是高,一條SQL發送過來到收到結果的上下文被切成不少片斷,同一時刻有來自不少不一樣上下文的不一樣的片斷要處理,全程只有一個進(線)程來處理這些片斷(暫且假設NIOReactor只配置成一個),因此在實現的過程當中要求把全部的細節都考慮很是周全,一旦某個片斷的處理有阻塞或者耗時,整個程序都將阻塞,我的以爲這種編程方式有點反人類思惟。
關於分佈式事務的思考
另外關於分佈式事務的支持也是一個你們可能比較感興趣的點,基於MySQL的方式來作分佈式數據庫的時候分佈式事務是不可能知足嚴格的分佈式事務語義的。
數據庫事務有ACID四個屬性,分別是原子性、一致性、隔離性、持久性。
原子性(Atomicity)的意思是整個事務最終只能是要麼成功要麼失敗,不能存在中間狀態,若是發生錯誤了就須要回滾回去,就像這個事務歷來沒有執行過同樣。
一致性(Consistency)是指系統要處於一個一致的狀態,不能由於併發事務的多少影響到系統的一致性,舉個典型的例子就是轉賬的狀況,假設有ABC三個賬號各有100元,那麼無論這三個賬號之間怎麼轉帳,整個系統總的額度是300元這一點是應該是不變的。其實ACID裏的一致性更多的是應用程序須要考慮的問題,和分佈式系統裏的CAP裏的一致性徹底不是一個概念。
隔離性(Isolation),本質上是解決併發執行的事務如何保證數據庫狀態是正確的,抽象描述叫可串行化,就是併發的事務在執行的時候效果要求達到看起來像是一個個事務串行執行的效果。有衝突的事務之間的隔離性若是保證不了會引發前面的一致性(consistency)也沒法知足。
每一個事務包含多個動做,這些動做若是按照事務自己的順序依次執行就是所謂的串行執行,這些動做也能夠從新排列,排列完之後的動做若是效果能夠等價於事務串行執行的效果咱們就叫作可串行化調度。
實際實現的時候每每採用的是衝突可串行化,這個條件比可串行化要求會更高一點,規定了一些讀寫順序規定了一些訪問衝突的狀況規定了哪些狀況兩個事物的動做能夠調換哪些是不能夠的,能夠理解爲衝突可串行化是可串行化的充分條件。
持久性(Durability),在事務完成之後全部的修改能夠持久的保存在數據庫中,通常會採用WAL的方式,會把操做提早記錄到日誌中來保證即便操做尚未刷到磁盤就宕機的狀況下有日誌能夠恢復。
介紹完事務的ACID屬性之後,咱們再來分析爲何基於MySQL沒法提供嚴格的分佈式事務語義的支持。
若是客戶端發送的SQL只涉及到一個節點,那天然是能夠保證事務的,可是若是客戶端發送的SQL涉及到兩個及以上節點的SQL,那就沒法保證事務語義了。
緣由主要是兩個,一是原子性沒法保證,另外一個是隔離性沒法保證。在一個節點commit成功之後,在另外的節點commit失敗了,這個事務就處在一箇中間狀態,此時原子性被打破。
引發的另外一個問題就是隔離性,這個事務的一部分提交了,另外一部分未提交,此時該事務正常是不應被讀取到的,可是提交成功的部分會被其餘事務讀到,此時就沒法保證隔離性了。
另外就算是涉及多個節點的操做都是成功的,理論上來講也是沒法保證隔離性的。由於假設A事務的一個節點先commit成功,其餘的節點後commit成功,而此時B事務在讀取的時候可能會讀取到了A事務最先commit成功的那部份內容,卻沒有讀到後來commit成功的內容,此時依然沒法保證隔離性。
更本質一點的緣由是MySQL的事務都是每一個實例維護自身的事務ID,而基於MySQL集羣的分佈式方案沒有一個全局的事務ID來標識每一個MySQL實例上的事務以及全局事務的元信息的管理,因此沒法作到嚴格的分佈式事務語義。
但實際上絕大多數業務對這個需求未必那麼強烈,由於絕大多數的業務邏輯都是能夠拆分的,拆成一個個只落在一個分庫裏的操做在絕大多數場景下是徹底可行的,並且拆分完之後也會更可控,因此這個問題在咱們支撐業務的過程當中也不是一個特別大的問題。
生產環境監控很關鍵
在實際生產環境中有不少方面都很是重要,高可用高可靠可擴展等,可是除了這些以外還有一個很是關鍵的是監控。
一個再健壯再牛x的系統都須要配備完善的監控系統,監控系統是生產環境中很是重要的一道防線,沒有監控的系統就像是在裸奔,線上突發情況不少完善的監控系統能夠作到第一時間發現問題及時定位以及解決問題。
物理機監控。
咱們在生產環境中會對系統所在物理機進行監控,京東有一個專門的物理機監控系統,能夠監控包括CPU、內存、網卡、TCP鏈接數、磁盤使用狀況、機器load等不少基礎指標,針對這些指標能夠設置相應的報警閾值,當超過必定閾值時會以郵件及短信的方式報警。
存活監控
但物理機的監控對於具體的系統的來講是遠遠不夠的,咱們還須要關注不少系統自己的信息,首先要有存活監控,這是最基本的。一個系統在線上運行的時候服務自己宕掉必定要求是能夠第一時間監控到的。
但除了物理機監控之外,還有一個很是關鍵的是存活監控。系統的一切前提是能夠活着,咱們在每一個模塊都會提供相應的http接口,接入公司的統一監控平臺,一旦有異常統一監控平臺會及時通知相應的負責人。
系統內部狀態可視化監控。
除了活下來之後,如何活得更好也是很關鍵的,因此咱們還有專門針對分佈式MySQL集羣的JMonitor系統,該系統會整合各個模塊的內部詳細狀態信息,包括慢查詢、用戶訪問狀況以及數據分佈狀況等。
一句話一個穩定健壯的系統必定要配備相應的完善的監控系統。今天個人分享就是這些,主要就是介紹一些分佈式MySQL的相關方案以及京東是怎麼作的,討論了一下分佈式事務的問題,最後是一小部分生產實踐經驗,謝謝你們。
Q&A
問題1:請介紹下分佈式事務保證數據最終一致性的具體方案例子。
首先分佈式事務涉及到的一致性和CAP中一致性是兩個概念,事務ACID屬性中的一致性不涉及最終一致性,對於關係型數據庫中事務的概念,個人理解都是強一致的(經過原子性和隔離型保證)。只有涉及到某一個節點(內容是相同的狀況)多副本之間的複製問題纔會涉及到弱一致性或者最終一致性(CAP中C)的問題。而分佈式事務自己若是保證了原子性和隔離性,數據庫層面就提供了一致性保證,其他的是應用邏輯層面保證。若是問的是數據庫主從複製之間的一致性問題,這個事情本質上和事務(ACID的C)的一致性就沒有關係了,因此這個問題自己可能有待商榷。
問題2:分佈式事務如何支持,如今能夠支持多大規模的集羣。
基於Mysql的分佈式集羣方案沒法保證嚴格的分佈式事務語義,可是在實際使用的時候看業務狀況,若是事務之間不怎麼衝突的狀況下也是ok的,若是能夠改爲只涉及一個分庫的狀況下那就繞開分佈式事務的問題了。另外支持的集羣,咱們實際上是根據業務來劃分資源的,目前總體資源不能說特別大,千臺規模。
問題3:JProxy是否能夠支持全部複雜sql查詢,主要是誇庫的關聯查詢,具體內部邏輯能否介紹下?
咱們目前不支持誇庫關聯查詢,從業務層面來解決。由於大表之間分庫之後若是要支持跨庫關聯查詢的話,做爲一個OLTP系統在實際生產環境估計就無法用了。
問題4:請介紹下MySQL實際應用中主從複製的方案,以及主從的數據差別會在什麼程度,謝謝!
這個其實更多的是主從之間關注的問題,通常會採用基於mix的模式。另外主從差別這個不一樣業務不同,加上嚴格的監控,正常訪問的狀況下通常不會出現延遲,可是若是涉及到業務倒數據或者突增的訪問量可能會引發延遲,因此這個不太好參考,若是有異常咱們都會第一時間及時介入處理。
問題5:長時間SQL不會形成堵塞嗎?
主要看這條SQL具體是作什麼的,若是是抽數據,就正常抽就能夠了。若是有阻塞基本都是由於在MySQL端由於鎖衝突等緣由形成阻塞,最終多是這個事務被abort掉或者最終搶到鎖成功作完了這個事務。
問題6:請介紹一下JTransfers的工做機制,以及實現過程當中最難的部分。
遷移確實是比較刺手的一件事情,要考慮的細節不少。大致的步驟是:咱們提交遷移計劃,指定什麼時間開始遷移,到時間點之後JTransfer就會自動遷移。JTransfer一開始是dump源分庫的數據,而後將這些數據恢復到目標實例上,可是在這個期間業務是正常訪問的,須要將增量數據遷移完,因此會有追增量過程。當增量追到必定程度,咱們會阻塞這個庫的訪問,最後將剩餘的少許數據遷移完。由於最後剩餘數據量很少的時候,阻塞過程其實很短暫,因此對業務影響很是小。
最難的部分是:整個遷移過程當中的路由變動,要保證路由變動的過程當中數據不能寫花,且變動之後的路由要準確的推送到JProxy中,由JManager和多個JProxy之間在變動路由的時候採用相似兩階段提交的協議,從而保證路由的變動是正確的。
問題7:能夠分享一下JProxy的併發性能優化,以及JProxy中間狀態的異常與恢復機制嗎?謝謝!
併發性能優化咱們主要是經過採用基於事件驅動的網絡模型,這種方式的特色是避免上下文切換避免鎖的開銷,可是代價的話剛纔也說了須要考慮得很是周全,把一個上下文切成不少片斷,不太符合人類思惟。
JProxy中間件狀態的異常與恢復機制這個我不是太理解什麼含義哈,個人理解是若是jproxy運行過程當中訪問出異常了怎麼處理,若是是某個鏈接過來的sql出了問題咱們的作法是將整個鏈接涉及到的資源都關閉,把該次查詢涉及到的先後端資源清理乾淨,這樣就不會影響到其餘客戶端的訪問。正常來講不該該出現這種狀況,因此也須要完善的日誌信息以及監控信息。
做者:meng_philip123 來源:http://www.jianshu.com/p/abb82c1a0657