基於mycat高可用方案——數據庫負載


引言

傳統企業級應用一般采取單台數據庫,吞吐所有應用的讀寫,隨着互聯網的高速發展,以及微服務架構越來越普及,往往采用分庫分表來支撐高速增長的大量業務數據吞吐。分庫分表主要有兩種方式:水平分表和垂直分庫。

  • 垂直分庫即基於業務層面,將不同業務數據存儲到不同的數據庫中。
  • 水平分表即把一個表的數據按照一定路由規則,路由到不同的數據庫,通常采用按照某個字段如ID作為路由因子,路由到不同庫。

架構演變

歷史架構

我們改造之前,數據庫層,基於業務做了垂直分庫,某些數據量大的業務比如用戶賬戶、賬務、訂單和優惠券等數據,都放到了同一個數據庫實例並水平將數據拆分為十幾個表,通過客戶端自定義路由來做讀寫分離和數據命中。數據庫架構如下圖。


 
歷史架構

這種架構會有以下問題:

  • 自定義路由策略單一,只能通過某個單一分庫因子路由到實際表
  • 自定義路由在當前數據庫配置不足以支撐業務需要水平擴展時,需要在重新實現路由規則,並經過嚴格的業務和性能測試
  • 自定義路由在客戶端水平擴展時,數據庫連接數量是無法控制的,比如,一個客戶端的數據庫連接pool設置為100,如果我由兩台擴展到4台,那么數據庫服務器接入的連接數最大就可能由原來的200增加到400,可能會導致數據庫服務器TCP連接數過多而內存不足或則宕機。

演變架構

目前系統采用單台HaProxy負載多台mycat服務器,通過mycat分庫分表,讀寫分離來管理和路由真實的多台mysql數據庫RDS。其配置部署圖如下:


 
當前架構部署圖

下面就講講為什么選用當前這種架構部署方案。

Why MyCat

MyCat是一款優秀的開源分布式數據庫中間件,架構設計圖如下。

 
mycat架構設計圖

具體實現原理這里就不多做贅述,它解決了哪些問題呢?

 

  • 連接過多問題,可以通過MyCat統一管理所有的數據源,更靈活的支持客戶端水平擴展,通過mycat統一管理連接池連接數量
  • 獨創的ER關系分片,解決E-R分片難處理問題,存在關聯關系的父子表在數據插入的過程中,子表會被MyCat路由到其相關父表記錄的節點上,從而父子表的Join查詢可以下推到各個數據庫節點上完成,這是最高效的跨節點Join處理技術,也是MyCat首創。
  • 采用全局分片技術,每個節點同時並發插入和更新數據,每個節點都可以讀取數據,提升讀性能的同時,也解決跨節點Join的效率。
  • 通過人工智能的catlet支持跨分片復雜SQL實現以及存儲過程支持等。

MyCat技術原理中最重要的一個動詞是“攔截”,它攔截了用戶發送過來的SQL語句,首先對SQL語句做了一些特定的分析:如分片分析、路由分析、讀寫分離分析、緩存分析等,然后將此SQL發往后端的真實數據庫,並將返回的結果做適當的處理,最終再返回給用戶。

Why HaProxy

HAProxy是一個使用C語言編寫的自由及開放源代碼軟件[1],其提供高可用性負載均衡,以及基於TCPHTTP的應用程序代理

HaProxy提供了高可用、負載均衡以及基於TCP和HTTP應用的代理,支持虛擬主機的高效可靠的解決方案,單純從效率上講比Nginx的負載均衡速度更優秀,關鍵是能兌Mysql數據庫進行負載均衡。
MyCat官方推薦的高可用架構部署方案就是基於HaProxy做的負載均衡。

 
MyCat官方推薦高可用架構

 

阿里雲SLB碰到的坑

我們在當前架構的實際應用中,踩過了不少的坑,可以說是一路泥濘。

  • 第一個要說的就是我們為什么沒采取阿里雲的SLB直接代理MyCat集群,剛開始設計架構的時候用的就是基於SLB做負載均衡,在項目運行一段時間后,發現MySql數據庫服務器的TCP連接源源不斷的增加,並且不會自我釋放連接。經過研究發現,MyCat在接收到SLB發送的健康檢查心跳的指令時,會把非mysql協議的指令直接發送給真實的mysql服務器,會建立大量MsqlIO連接,而mysql數據庫無法識別這些指令就導致數據庫連接泄露,這也算是Mycat的一個Bug,目前Mycat還沒有解決該問題。

如果誰有好的解決方案,請跟筆者聯系。

  • 我們用了MyCat官方推薦的基於多台HaProxy+KeepAlived+MyCat的集群架構,但是阿里雲的上ECS服務器跟我們自己的虛擬機不一樣,它不支持浮動IP的,所以我們無法使用keepalived方案,雖然現在可以利用阿里雲VIP解決這個問題,但是阿里雲服務器上keepalived只能設置單播,且要做當主庫宕機恢復后,容易出現IP漂移的問題,所以我們也放棄了這個方案。

當前問題和解決方案

基於上述現有數據庫部署架構,我們肯定不難發現,如果單點HaProxy宕機,對我們整個生產環境系統來說,就是災難性的雪崩,我們就做了一下解決方案:

  • 方案一:基於Spring技術,自己開發了一套數據庫客戶端軟負載的中間件,可以在數據庫連接池中獲取連接時,通過負載均衡器從動態數據源集合對象中后去一個數據源,並在初始化時啟動一個線程監控所有mycat連接,定時進行健康檢查,並根據結果更新負載均衡數據源集合對象中的數據源,其原理圖如下:


     
    自定義數據庫軟負載原理圖
  • 方案二:基於MySql Connector/J java連接器的LoadBalancing協議,進行負載均衡。
    下面我們着重介紹Mysql Connector的loadbalancing協議

Load Balancing協議與Failover協議

介紹loadbalancing協議之前我們先了解下Failover協議,loadbalancing協議就是基於Failover協議。

Failover協議

Failover協議是mysql的失效重連協議,即當客戶端鏈接失效時,會嘗試與其他host建立鏈接。其URL格式如下:

jdbc:mysql://[master-host]:[port],[slaves-host]:[port],.../[database]?[property=<value>]&[property=<value>]

master服務器需要位於hosts列表第一個,當創建一個connection時,mysql connector/J驅動會首先嘗試和master host連接,如果出現異常,則依次和slaves中的host建立連接,直到成功為止;;即使與master的連接失效,但是mysql connector/J驅動並不會丟失master的特殊狀態:比如它的訪問模式、優先級等。當所有host都不可用,那么mysql connector/J驅動會繼續輪詢,直到次數達到閾值,通過屬性retriesAllDown控制,默認120次,達到閾值仍然無法獲取連接則跑出SQLException。
當master恢復后,mysql connector/J驅動又能自動的FallBack到master主機,基於這個特性,我們就能做一些故障容災和自動恢復。

參見源碼:(FailoverConnectionProxy)

Load Balancing(LB)協議

由於Failover協議沒有load balance的特性,它讀/寫操作總是只發生在同一個host上,然而我們實際應用中,可能會出現‘replication’,‘多Master’的情況,比如我們mycat部署多台,需要負載均衡保證高可用,load balancing協議就應運而生了。LB協議可以將讀/寫負載到多個Mysql實例上,這些實例通常是‘replication’架構和集群架構。LB協議基於Failover協議,其URL格式為:

jdbc:mysql:loadbalance://[host]:[port],[host]:[port],...[/database]?[property=<value>]&[property=<value>]

mysql connector/J驅動創建的LoadBalancedConnection是一個邏輯鏈接,其內部持有一個物理鏈接列表,即與每個host建立一個Connection。url中的每個host都是平等的主host,當客戶端獲取連接時會有兩種random(默認隨機)bestResponseTime(最小響應時間)兩種均衡策略,可以在參數** loadBalanceStrategy**中指定。

(參見源碼:LoadBalancedConnectionProxy,BalanceStrategy,LoadBalancedConnection)

  • 當autocommit為false時,在事務的邊界方法執行后,比如commit、rollback,將會觸發BalanceStrategy從host列表中重新選擇新的鏈接。
  • 當鏈接上發生Exception時,比如socket異常,將會導致重新選擇鏈接。
  • 當autocommit為true時,當鏈接上執行“loadBalanceAutoCommitStatementThreshold”個statements后(Queries或者updates等),將會導致重新選擇鏈接,默認為0表示“粘性鏈接,不重新選擇鏈接”。(這是負載均衡的一種補償措施)

方案選擇

兩種方案我們都在開發、測試環境甚至生成環境做過很多對數據庫讀寫的測試操作,對比后做了以下總結:

  • 方案一自定義軟負載是自己實現的,功能單一,負載策略只有輪詢,而且健康檢查在很長一段時間出現連接泄露的問題,穩定性不夠好,還需要做一些優化和測試進行打磨;
  • 方案二是基於mysql的java連接驅動做的負載均衡,是官方提供的方案,穩定性和可用性更高,而且我們在經過很長一段時間的壓測和容錯性測試,發現其性能很優越,負載均衡策略也更豐富,使用過程更簡單。

值得注意的是其中的autoReconnect、** autoReconnectForPools**這兩個參數參數用於控制“重連”特性,即當一個session或者事務操作過程中,如果鏈接出現異常,driver不會跑出Exception而是嘗試重新連接並繼續執行,如果是非事務操作如查詢,則該參數不會帶來問題;如果是事務操作,在事務多個操作過程中發生重連,就有可能對session狀態造成破壞,而導致數據不一致問題,即便開啟autocommit=false仍然不能完全避免這個問題,事實上重連之后並不會rollback原來中斷的事務,而是繼續進行,參見【autoReconnect】。此參數選項將會在未來的版本中被移除,官方也建議禁用此特性。

總結

我們選擇了基於msyql connector/J驅動的LB協議的數據庫客戶端高可用方案,其部署架構圖如下:

 
基於數據庫LB協議部署架構圖

 

我建議數據庫URL配置為一下格式:

jdbc:mysql:loadbalance://[host]:[port],[host]:[port],...[/database]?useUnicode=true&characterEncoding=utf-8&zerodatetimebehavior=converttonull&allowMultiQueries=true

  • useUnicode=true&characterEncoding=utf-8:指定字符的編碼、解碼格式。
  • zerodatetimebehavior=converttonull:JAVA連接MySQL數據庫,在操作各項值均為為0(或者有0不正確的數據)的timestamp等(日期為0000-00-00。。。。)類型時不能正確處理,而是默認拋出一個異常,比如所見的:java.sql.SQLException: Cannot convert value '0000-00-00 00:00:00' from column XX to TIMESTAMP
  • allowMultiQueries=true:可以允許一次執行多條sql(通過分號分割)



作者:愚行者
鏈接:https://www.jianshu.com/p/4c4641d4a566
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM