本系列會分析OpenStack 的高可用性(HA)概念和解決方案:
(2)Neutron L3 Agent HA - VRRP (虛擬路由冗余協議)
(3)Neutron L3 Agent HA - DVR (分布式虛機路由器)
(4)Pacemaker 和 OpenStack Resource Agent (RA)
(5)RabbitMQ HA
(6)MySQL HA
1. MySQL HA 方案
1.1 各種方案概述
Mysql HA 方案有很多種,包括:
- mmm: http://mysql-mmm.org/
- mha: https://code.google.com/p/mysql-master-ha/
- heartbeat+brdb: http://lin128.blog.51cto.com/407924/279411 http://www.centos.bz/2012/03/achieve-drbd-high-availability-with-heartbeat/
- cluster(使用ndb引擎):http://database.51cto.com/art/201008/218326.htm
- 雙master+keeplived: http://database.51cto.com/art/201012/237204.htm,http://kb.cnblogs.com/page/83944/
- 雙master: http://yunnick.iteye.com/blog/1845301
- Oracel Fabric 方案:http://www.csdn.net/article/2014-08-20/2821300
這些高可用方案,大多是基於以下幾種基礎來部署的:
- 基於主從復制;
- 基於Galera協議;
- 基於NDB引擎;
- 基於中間件/proxy;
- 基於共享存儲;
- 基於主機高可用;
從 SLA 的角度看:
- 要達到99.9%:使用MYSQL復制技術
- 要達到99.99%:使用MYSQL NDB 集群和虛擬化技術
- 要達到99.999%:使用shared-nothing架構的GEO-REPLICATION和NDB集群技術
這里 有個各種方案的比較:
在這些可選項中,最常見的就是基於主從復制的方案,其次是基於Galera的方案。這篇文章 分享MYSQL中的各種高可用技術 全面具體地分析了 Mysql 的各種容災方案。也可見 Mysql 容災的水很深。
1.2 MySQL Cluster(NDB Storage Engine)
MySQL Cluster 是 MySQL 官方也就是 Oracle 主推的一種提供去中心化集群(shared-nothing clustering)和 自動共享(auto-sharding)的MySQL 數據庫管理系統。它被設計來提供高可用(99.999%)、高吐吞吐量、低延遲和幾乎線性擴展的解決方案。它是基於 MySQL 的 NDB 或者 NDBCLUSTER 存儲引擎實現的。(引用自 https://en.wikipedia.org/wiki/MySQL_Cluster)
官網:https://www.mysql.com/products/cluster/
版本:MySQL Cluster 有獨立於 MySQL 的版本(7.4版本使用 MySQL 5.6;7.3版本使用 MySQL 5.5)
價格:https://www.mysql.com/products/
這是 Choosing the right MySQL High Availability Solution – webinar replay MySQL Cluster 和其它幾個主要HA方案的 SLA 比較:
(1)特征
(2)架構
- 1)Sql結點(SQL node--上圖對應為MySQLd):分布式數據庫。包括自身數據和查詢中心結點數據.
- 2)數據結點(Data node -- ndbd):集群共享數據(內存中).
- 3)管理服務器(Management Server – ndb_mgmd):集群管理SQL node,Data node.
支持最多 48 個 data nodes;集群最多 255 個節點
2. MariaDB 和 Galera Cluster
MySQL 被 Oracle 收購后,基於需求以及對 Oracle 的擔心,出現了兩個主要的分支。它們都是免費開源的軟件。
2.1 MySQL 的兩個主要分支之一之 MariaDB
MariaDB由MySQL的創始人麥克爾·維德紐斯主導開發,他早前曾以10億美元的價格,將自己創建的公司MySQL AB賣給了SUN,此后,隨着SUN被甲骨文收購,MySQL的所有權也落入Oracle的手中。MariaDB名稱來自麥克爾·維德紐斯的女兒瑪麗亞(英語:Maria)的名字。
MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能輕松成為MySQL的代替品。在存儲引擎方面,10.0.9版起使用XtraDB(名稱代號為Aria)來代替MySQL的InnoDB。
版本方面,MariaDB直到5.5版本,均依照MySQL的版本。因此,使用MariaDB5.5的人會從MySQL 5.5中了解到MariaDB的所有功能。從2012年11月12日起發布的10.0.0版開始,不再依照MySQL的版號。10.0.x版以5.5版為基礎,加上移植自MySQL 5.6版的功能和自行開發的新功能。
相對於最新的MySQL5.6,MariaDB在性能、功能、管理、NoSQL擴展方面包含了更豐富的特性。比如微秒的支持、線程池、子查詢優化、組提交、進度報告等。
官網地址:https://mariadb.org/
2.2 MySQL 的兩個主要分支之二之 Percona
Percona Server就是這樣一款產品,由領先的MySQL咨詢公司Percona發布。Percona Server是一款獨立的數據庫產品,為用戶提供了換出其MySQL安裝並換入Percona Server產品的能力。通過這樣做,就可以利用XtraDB存儲引擎。Percona Server聲稱可以完全與MySQL兼容,因此從理論上講,您無需更改軟件中的任何代碼。這確實是一個很大的優勢,適合在您尋找快速性能改進時控制質量。因此,采用Percona Server的一個很好的理由是,利用XtraDB引擎來盡可能地減少代碼更改。
更多的比較,可以參考網上的大量文章,比如
2.3 Galera Cluster
Galera Cluster 是一套在innodb存儲引擎上面實現multi-master及數據實時同步的系統架構,業務層面無需做讀寫分離工作,數據庫讀寫壓力都能按照既定的規則分發到各個節點上去。在數據方面完全兼容 MariaDB 和 MySQL。
官網:http://galeracluster.com/products/
使用案例:HP, OpenStack,KPN
特征:
2.4 MySQL Galera Cluster
2.4.1 MySQL Galera Cluster 的特點和局限
Galera Cluster 可以同時支持 MySQL 和 MariaDB:
- 安裝 MySQL Galera Cluster:需要安裝帶 wsrep patch 的MySQL版本(比如 MySQL 5.5.29)和 Galera復制插件,詳細步驟請參考 MySQL多主復制-MySQL Galera安裝部署
- 安裝 MiraDB Galera Cluster:參考 OpenStack 在 RedHat 平台上的 MariaDB HA 方案,以及 MariaDB Galera Cluster 部署。
為了支持MySQL,Galera Cluster 中使用了由 Coreship 提供的補丁 (https://launchpad.net/codership-mysql)。MySQL Galera 集群:
- 使用通用的 Wsrep replication 來替代 MySQL Cluster 中的 Replication
- 基本 Quorum的集群,最少三個節點,只能奇數個節點
- 使用偶數個節點時,可以使用一個 Galera Arbiter (garbd)
- 較高的死鎖可能性:在多主集群中,不支持對表加鎖和解鎖(LOCK/UNLOCK TABLES cannot be supported in multi-master setups)。因此,在兩個transaction 從不同的節點更新同一個 row 的時候,只有一個 transaction 會成功,對另外一個transaction,MySQL 會返回死鎖錯誤(Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK))。
- 部署:
- MySQL Galera 集群有非常多的使用限制(來源):
In MySQL-5.5.x/wsrep-23.x, Galera Replication has some limitations, these are documented in readme-wsrep.
Galera replication originally only worked with InnoDB storage engine, but it now also supports MyISAM storage engine. Any writes to other table types, including system (mysql.*) tables are not replicated. However, DDL statements are replicated in statement level, and changes to mysql.* tables will get replicated that way. So, you can safely issue: CREATE USER..., but issuing: INSERT INTO mysql.user..., will not be replicated.
MyISAM replication is recent and should be considered experimental. Non-deterministic functions like NOW() are not supported. The Configurator for Galera enables wsrep_replicate_myisam by default.
DELETE operation is unsupported on tables without primary key. Also rows in tables without primary key may appear in different order on different nodes. As a result SELECT...LIMIT... may return slightly different sets.
Unsupported queries:
* LOCK/UNLOCK TABLES cannot be supported in multi-master setups. 不支持跨節點的表鎖
* lock functions (GET_LOCK(), RELEASE_LOCK()... )
Query log cannot be directed to table. If you enable query logging, you must forward the log to a file:
log_output = FILE
Use general_log and general_log_file to choose query logging and the log file name.
Maximum allowed transaction size is defined by wsrep_max_ws_rows and wsrep_max_ws_size. Anything bigger (e.g. huge LOAD DATA) will be rejected.
Due to cluster level optimistic concurrency control, transaction issuing COMMIT may still be aborted at that stage. There can be two transactions writing to same rows and committing in separate cluster nodes, and only one of the them can successfully commit. The failing one will be aborted. For cluster level aborts, MySQL/galera cluster gives back deadlock error.
code (Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK)).
XA transactions can not be supported due to possible rollback on commit.
MySQL 5.6 的同樣描述在 percona/debian-percona-xtradb-cluster-5.6
原因和概率:
-
- MySQL Galera 在本地節點上使用悲觀鎖(pessimistic locking)
- MySQL Galera 在其它節點上使用樂觀鎖(optimistic locking)
- 在大的負載壓力下的發生概率大概為 1/500
- 在正常的負載壓力下的發生概率大概為 1/10000
2.4.2 NDB (MySQL Cluster 使用的引擎)和 MySQL Galera Cluster 的性能對比
2.4.3 一些 best practice
常規的推薦做法:
減少死鎖的一些推薦做法:
3. 使用 Pacemaker + DRBD + CoroSync 的 A/P 方案
與 RabbitMQ HA 方案類似,OpenStack 官方推薦的 Mysql Active/Passive HA 方案也是 Pacemaker + DRBD + CoroSync。具體方案為:
- 配置 DRBD 用於 Mysql
- 配置 Mysql 的 var/lib/mysql 目錄位於 DRBD 設備上
- 選擇和配置一個 VIP,配置 Mysql 在該 IP 上監聽
- 使用 Pacemaker 管理 Mysql 所有的資源,包括其 deamon
- 配置 OpenStack 服務使用基於 VIP 的 Mysql 連接
OpenStack 官方推薦的 Mysql HA A/P 方案 配置完成后的效果:
這個文檔 詳細闡述了具體的配置步驟。這個方案的問題是,drbd 容易出現腦裂;而且,兩個 mysql 節點只有一個能提供服務,存在資源浪費。
4. 使用 MySQL Galera 的多主方案
4.1 三節點方案
架構如下:
Galera 主要功能:
- 同步復制
- 真正的multi-master,即所有節點可以同時讀寫數據庫
- 自動的節點成員控制,失效節點自動被清除
- 新節點加入數據自動復制
- 真正的並行復制,行級
- 用戶可以直接連接集群,使用感受上與MySQL完全一致
優勢:
- 因為是多主,所以不存在延遲
- 不存在丟失交易的情況
- 同時具有讀和寫的擴展能力
- 更小的客戶端延遲
- 節點間數據是同步的,而Master/Slave模式是異步的,不同slave上的binlog可能是不同的
局限:見 4.3.1 章節的詳細描述
另外,MySQL Galera Cluster 並不是適合所有需要復制的情形,你必須根據自己的需求來決定,比如,
- 如果你是數據一致性考慮的多,而且寫操作和更新的東西多,但寫入量不是很大,MySQL Galera Cluster就適合你。但是,這種方案中整個集群的寫入吞吐量是由最弱的節點限制,如果有一個節點變得緩慢,那么整個集群將是緩慢的。為了穩定的高性能要求,所有的節點應使用統一的硬件,而且集群節點建議最少3個。
- 如果你是查詢的多,且讀寫分離也容易實現,那就用 replication 好,簡單易用,用一個 master 保證數據的一致性,可以有多個slave用來讀去數據,分擔負載,只要能解決好數據一致性和唯一性,replication就更適合你,畢竟 MySQL Galera Cluster集群遵循“木桶”原理,如果寫的量很大,數據同步速度是由集群節點中IO最低的節點決定的,整體上,寫入的速度會比replication慢許多。
詳細配置過程可以參考 OpenStack HA Guide, 這個文章 和 MySQL Multi-master Replication With Galera。
4.2 兩節點 + Galera Arbitrator A/A HA 方案
3.1 中的A/A 方案需要三個節點,因此成本比較高。本方案提供使用兩個節點情況下的 A/A 方案。相信信息可以參考 這篇文章。
該方案使用 Arbitrator 作為第三個節點來使用,它其實是一個守護進程。它有兩個作用:
- 當你使用偶數個節點時,它能當作一個奇數節點使用,來防止腦裂發生。
- 它能被用作持續的系統狀態的快照,用於備份目的。
4.3 OpenStack 使用 HAProxy + MySQL Galera Cluster 的問題
這篇文章中,作者對200個OpenStack用戶/運維人員做過一個關於數據庫使用的調查,結果是
- 1 人使用 PostgreSQL
- 10幾個人使用標准的 MYSQL master/slave replication 方案
- 其他人都是用 MySQL Gelera 集群
4.3.1 問題原理
OpenStack 官方推薦的A/A HA 方案是使用 Galera 來做三節點HA(http://docs.openstack.org/ha-guide/controller-ha-galera.html)。這種模式下,Galera 提供多個 Mysql 節點之間的同步復制,使得多個 Mysql 節點同時對外提供服務,這時候往往需要使用負載均衡軟件比如 HAProxy 來提供一個 VIP 給各應用使用。但是,OpenStack 文檔回避了這種MySql集群的問題。
對於 2.4.1 部分描述的 MySQL Galera 的一些局限,如果在 OpenStack 環境中使用 HAProxy 做整個MySQL Galera 的 LB 的話, 因為該集群不支持跨節點對表加鎖,也就是說如果OpenStack 某組件有兩個會話分布在兩個節點上同時寫入某一條數據,那么其中一個會話將會遇到死鎖的情況。網上這種情況的報告非常多,比如:
- https://bugzilla.redhat.com/show_bug.cgi?id=1141972 Cause: Unhandled database deadlock conditions triggered with some database configuration edge cases,Consequence: Lost database transactions,Fix: Retry actions on deadlock conditions,Result: Robust database communication in all cases
- http://www.gossamer-threads.com/lists/openstack/operators/41337 Openstack and mysql galera with haproxy
文章 Avoiding Deadlocks in Galera - Set up HAProxy for single-node writes and multi-node reads 對這個問題和解決方法有非常詳細的描述。基本原理示意圖:
有一個長長的 OpenStack 郵件列表,IMPORTANT: MySQL Galera does *not* support SELECT ... FOR UPDATE (寫於 2014年五月)中,作者列出了Nova 和 Neutron 中使用的 SELECT ... FOR UPDATE 代碼,其中
- Nova 中只有若干地方使用該結構,但是,使用的地方是 quota 代碼中
- Neutron 中大量使用該結構,據統計,在11個不同的文件中出現了44次。
可見,在寫並發非常高的情況下,死鎖的情況的出現概率是不低的,特別是在 Neutron 中。
作者還提出了幾個選項:
- 在包含 with_lockmode('update') 代碼的 OpenStack 模塊中不使用 MYSQL Galera 作為數據庫(問題是目前沒更好的選擇。。)
- 在 OpenStack 文檔中加入有關問題的注釋(這么目前都還沒有。。)
- 修改 Nova 和 Neutron 的代碼,替換掉 with_lockmode('update') 代碼 (據說,目前,Nova 代碼的修改已經完成,Neutron 還沒有。。)
- 對於 Nova db quota 驅動,做代碼優化
4.3.2 一些 workaround
(1)上面的郵件回復中提到的一個 workaround,就是使得更新請求只發往一個節點。在使用 HAProxy 的情況下,具體做法是,只設定一個節點為 master,其余的為 backup。HAProxy 會在 master 失效時自動切換到某一個 backup 上。
server 192.168.0.101 192.168.0.101:3306 check server 192.168.0.102 192.168.0.102:3306 check backup server 192.168.0.103 192.168.0.103:3306 check backup
如果還需要進一步優化的話,可以只將寫操作放到一個節點,而將讀操作在所有節點之間做負載均衡從而提高性能。Percona XtraDB Cluster reference architecture with HaProxy 描述了一個改進的方案,就是提供兩個 MYSQL 服務端點,一個(端口 3306)只是使用(包括讀寫)一個節點,另一個(端口3306)使用三個節點。因此,對 OpenStack 來說,Neutron 使用 3306 端口(如果Nova解決了問題的話),其它組件使用 3307 端口。
global log 127.0.0.1 local0 log 127.0.0.1 local1 notice maxconn 4096 chroot /usr/share/haproxy user haproxy group haproxy daemon defaults log global mode http option tcplog option dontlognull retries 3 option redispatch maxconn 2000 contimeout 5000 clitimeout 50000 srvtimeout 50000
frontend pxc-front bind *:3307 mode tcp default_backend pxc-back
frontend stats-front bind *:80 mode http default_backend stats-back
frontend pxc-onenode-front bind *:3306 mode tcp default_backend pxc-onenode-back
backend pxc-back #master-master,適用於沒有使用 SELECT...UPDATE 語句的應用 mode tcp balance leastconn option httpchk server c1 10.116.39.76:3306 check port 9200 inter 12000 rise 3 fall 3 server c2 10.195.206.117:3306 check port 9200 inter 12000 rise 3 fall 3 server c3 10.202.23.92:3306 check port 9200 inter 12000 rise 3 fall 3
backend stats-back mode http balance roundrobin stats uri /haproxy/stats stats auth pxcstats:secret
backend pxc-onenode-back #一個master,其它是backup,用來避免deadlock mode tcp balance leastconn option httpchk server c1 10.116.39.76:3306 check port 9200 inter 12000 rise 3 fall 3 server c2 10.195.206.117:3306 check port 9200 inter 12000 rise 3 fall 3 backup server c3 10.202.23.92:3306 check port 9200 inter 12000 rise 3 fall 3 backup
(2)另外一個方案是,將 OpenStack 所有的MySQL 操作按照讀和寫做分離(read write splitting),寫只在那個主節點上,讀在所有節點上做負載均衡。但是,目前 OpenStack 應該還沒有原生的支持。一個可選的方案是使用開源軟件 maxscale:https://www.percona.com/blog/2015/06/08/maxscale-a-new-tool-to-solve-your-mysql-scalability-problems/
(3)關於 openstack 里面的讀寫分離,其 db 庫 oslo 倒是有了接口支持:
def get_session(self, use_slave=False, **kwargs):
"""Get a Session instance.
:param use_slave: if possible, use 'slave' database connection for this session. If the connection string for the slave database wasn't provided, a session bound to the 'master' engine will be returned. (defaults to False)
:type use_slave: bool
但是從代碼(Kilo版本)來看,只有 Nova 支持這種 slave_connection 參數(\nova\nova\db\sqlalchemy\api.py):
def model_query(context, model, args=None, session=None, use_slave=False, read_deleted=None, project_only=False): if session is None: if CONF.database.slave_connection == '': use_slave = False session = get_session(use_slave=use_slave)
而 Neutron 模塊至少在 Kilo 版本中還沒有實現。下面的代碼中,調用 oslo 創建 db session 的時候,根本就沒有傳入 CONF.database.slave_connection 的值:
def get_session(autocommit=True, expire_on_commit=False): """Helper method to grab session.""" facade = _create_facade_lazily() return facade.get_session(autocommit=autocommit, expire_on_commit=expire_on_commit)
在 這個 openstack 郵件列表 中也能確認這個狀態:
Nova is the only project that uses slave_connection option and it was kind of broken: nova bare metal driver uses a separate database and there was no way to use a slave db connection for it.
但是,在 Juno 版本中,唯一沒有解決 dead lock 問題的就是 Neutorn,因此,該配置項對解決 Neutron MySQL Galera 死鎖沒有實質性意義。它只對Nova提高DB性能有幫助。關於該配置項,可以參考官方文檔 https://wiki.openstack.org/wiki/Slave_usage。
4.3.3 對該問題的終極處理
OpenStack 的 Nova 和 Neutorn 模塊都在不少地方使用了 SELECT... FOR UPDAT 語句,可以參考 A lock-free quota implementation 文章中的描述。因此,如果不想使用上面的 Workaround(它降低了對擴展性的支持)而要做終極處理的話,就需要修改Nova 和 Neutron 的代碼將這些語句替換掉了。
關於代碼修改,有如下文檔:
[documented]:
[Nova]:
http://specs.openstack.org/openstack/nova-specs/specs/kilo/approved/lock-free-quota-management.html
https://bugs.launchpad.net/oslo.db/+bug/1394298 Galera deadlock on SELECT FOR UPDATE is not handled。這個針對 Nova 的 fix 已經進了 Kilo 版本。
[Neutron]:
https://bugs.launchpad.net/neutron/+bug/1364358https://bugs.launchpad.net/neutron/+bug/1331564
https://bugs.launchpad.net/neutron/+bug/1364358 Remove SELECT FOR UPDATE usage。該 ticket 目前還是 incomplete 狀態,而最新的注釋 “the current design disallows to remove all SELECT FOR UPDATE so the right bug would to ensure all SELECT FOR UPDATE are Galera multi-writers compliant” 更是說明Neutron 這部分的修改還沒有完成。因此,對於 Neutron 來說,還得繼續使用 workaround。
根據 Mirantis 和 Percona 的 這個報告,Juno 版本中,“All components except neutron are good with using multiple writers”。
4.3.4 數據最終一致性問題
這個 OpenStack 郵件列表 描述了該問題:
A: start transaction; A: insert into foo values(1) A: commit; B: select * from foo; <-- May not contain the value we inserted above[3]
這說明,雖然 Galera 是生成同步的,但是作為分布式數據庫,本質上還是需要一些時間,即使非常短,完成寫入的數據同步到整個集群的。因此,在某些情況下,特別是MySQL 負載很大導致同步壓力很大的情況下,這種讀寫不一致性的問題可能會更加突出。
注意最下面右側的Node1 和 node2 上的箭頭和紅線直接的差距(藍色圓圈內),這也是為什么是 “virtually”同步,而不是直接同步。如果正好在gap時間段內讀的話,是無法讀到寫入的數據的。
好在 MySQL Galera 提供了一個配置項 wsrep_sync_wait,它的含義是 “Defines whether the node enforces strict cluster-wide causality checks.” ,可以有如下值:
Bitmask | Checks |
---|---|
0 | Disabled. |
1 | Checks on READ statements, including SELECT, SHOW, and BEGIN / STARTTRANSACTION. |
2 | Checks made on UPDATE and DELETE statements. |
3 | Checks made on READ, UPDATE and DELETE statements. |
4 | Checks made on INSERT and REPLACE statements. |
它的默認值是0,如果需要保證讀寫一致性可以設置為1。但是需要注意的是,該設置會帶來相應的延遲性,因此,它是一把雙刃劍,到底對性能有多大的影響,需要經過測試才能使用。關於該配置項和其它 wresp 配置項的具體說明,可以參考 http://galeracluster.com/documentation-webpages/mysqlwsrepoptions.html。注意到 Mirantis 和 Percona 的 這個報告 所使用的測試環境中該值被設為1了。
4.3.5 小結
看起來,目前至少Neutron 還沒有完成代碼修改,Nova 中的代碼修改看似已經完成,但是需要通過測試來驗證。因此,目前情況下,我們還是需要使用 4.3.1 中描述的 workaround。
參考鏈接:
- http://leejia.blog.51cto.com/4356849/841084
- http://drbd.linbit.com/
- http://kafecho.github.io/presentations/introduction-to-pacemaker/#/8
- http://chuansong.me/n/412792
- http://www.gpfeng.com/?p=603
- http://fengchj.com/?p=2273
- http://imysql.com/2015/09/14/solutions-of-mysql-ha.shtml
- http://www.joinfu.com/2015/01/understanding-reservations-concurrency-locking-in-nova/
- http://www.clusterdb.com/mysql/choosing-the-right-mysql-high-availability-solution-webinar-replay
- http://galeracluster.com/documentation-webpages/mysqlwsrepoptions.html