何谓Galera Cluster?就是集成了Galera插件的MySQL集群,是一种新型的,数据不共享的,高度冗余的高可用方案,目前Galera Cluster有两个版本,分别是Percona Xtradb Cluster和MariaDB Cluster,都基于Galera,所以这里都统称为Galera Cluster,因为Galera本身具有多主特性,所以Galera Cluster也就是Multi-Master的集群架构,如图1所示。
图1中有三个实例,组成了一个集群,而这三个节点与普通主从架构不同,都可作为主节点,三个节点对等,这种一般称为Multi-Master架构,当有客户端要写入或读取数据时,随便连接哪个实例都一样,读到的数据相同,写入某一节点后,集群自己会将新数据同步到其他节点上,这种架构不共享任何数据,是一种高冗余架构。
一般使用方法是,在这个集群上再搭建一个中间层,这个中间层的功能包括建立连接,管理连接池,负责使三个实例的负载基本平衡,负责在客户端与实例的连接断开之后重连,也可以负责读写分离(在机器性能不同的情况下可以做这样的优化)等,使用这个中间层后,由于这三个实例的架构在客户端方面是透明的,客户端只需要指定这个集群的数据源地址,连接到中间层即可,中间层会负责客户端与服务器实例连接的传递工作,由于这个架构支持多点写入,所以完全避免了主从复制经常出现的数据不一致问题,从而可以做到高度优雅的主从读写切换,在不影响用户的情况下,进行离线维护等工作,MySQL的高可用从此开始,非常完美。
这里最核心的问题其实是:在三个实例之间,因为它们关系对等,那么在同时写入时,如何保证整个集群数据的一致、完整与正确?
通常在使用MySQL的过程中,也不难实现一种Multi-Master架构,但是一般需要上层应用来配合,比如先要约定每个表必须有自增列,并且如果是2个节点的情况,一个节点只能写偶数值,而另一个节点只能写奇数值,同时2个节点之间互相做复制,因为2个节点写入的东西不同,所以复制不会冲突,在这种约定之下,可以基本实现多Master架构,也可以保证数据的完整性与一致性。但这种方式使用起来还是有限制,同时还会出现复制延迟,且不具有扩展性,不是真正意义上的集群。
通过这些API,Galera Cluster提供了基于验证的复制,是一种乐观的同步复制机制,一个将要被复制的事务(称为写集),不仅包括被修改的数据库行,还包括这个事务产生的所有Binlog,每一个节点在复制事务时,都会拿这些写集与正在APPLY队列的写集做比对,如果没有冲突,这个事务就可以继续提交,或APPLY,此时就认为这个事务被提交了,然后在数据库层面,还需要继续做事务上的提交操作。
这种方式的复制,也被称为是虚拟同步复制,实际上是一种逻辑上的同步,因为每个节点的写入和提交操作还是独立的,更准确的说是异步的。Galera Cluster建立在一种乐观复制的基础上,假设集群中的每个节点都同步,加上在写入时都会做验证,那么理论上是不会出现不一致的,当然也不能这么乐观,如果出现不一致,比如主库(相对)插入成功,而从库则出现主键冲突,那说明此时数据库已经不一致,这种情况下Galera Cluster采取的方式是将出现不一致数据的节点踢出集群,其实是自己shutdown了。
而通过使用Galera,它在其中通过判断键值的冲突方式实现了真正意义上的Multi-Master,Galera Cluster在MySQL生态中,在高可用方面实现了非常重要的提升,目前Galera Cluster具备的功能包括以下几个方面:
以上几点,足以说明Galera Cluster是一个既稳健,又在数据一致性、完整性和高性能方面有出色表现的高可用解决方案。
目前熟知的一些特性,或者在运维中需要注意的一些特性,有以下几个方面:
Galera Cluster写集内容
Galera Cluster复制的方式,仍基于Binlog,很多人也一直为此困扰,因为目前Percona Xtradb Cluster所实现的版本中,将Binlog关掉后,还可以使用,这误导了很多人,其实关掉之后,只是不落地了,表象上看上去没有使用Binlog,实际上内部还是悄悄打开了。除此之外,写集中还包括事务影响的所有行的主键,所有主键组成了写集的KEY,而Binlog组成了写集的DATA,这样一个KEY-DATA就是写集。
KEY和DATA分别具有不同的作用,KEY用来验证与其它事务没有冲突,而DATA则在验证通过后做APPLY。
Galera Cluster的并发控制
从前文可以得知,Galera Cluster可以实现集群中数据的高度一致性,并且在每个节点上生成的Binlog顺序都一样,这与Galera内部实现的并发控制机制是分不开的。所有的上层到下层的同步、复制、执行、提交都通过并发控制机制来管理。这样才能保证上层的逻辑性,下层数据的完整性等。
图2截取自官方手册,从图中可以大概看出,从事务执行开始,到本地执行,再到写集发送,再到写集验证,再到写集提交的整个过程,以及从节点(相对)收到写集后,所做的写集验证、写集APPLY和写集提交操作,通过对比该图,可以很好地理解每一个阶段的意义及性能等,下面就每一个阶段以及其并发控制行为做一个简单介绍:
这个阶段,是事务执行的最初阶段,可以说,这个阶段的执行过程,与单点MySQL执行没什么区别,并发控制当然就是数据库的并发控制,而不是Galera Cluster的并发控制。
执行完之后,就到了提交阶段,提交之前首先将产生的写集广播出去,而为了保证全局数据的一致性,在写集发送时,需要串行,这就属于Galera Cluster并发控制的一部分。
这个阶段,就是我们通常说的Galera Cluster的验证,验证是将当前事务与本地写集验证缓存集做验证,通过比对写集中被影响的数据库KEYS,来发现有没有相同的,来确定是不是可以验证通过,那么这个过程,也是串行的。
这个阶段,是一个事务执行的最后一个阶段,验证完成后,就可以进入提交阶段,因为此时已经执行完,而提交操作的并发控制可以通过参数来控制其行为,即参repl.commit_order,如果设置为3,表示提交就是串行的,而这也是本人所推荐的(默认值)一种设置,因为这样的结果是,集群中不同节点产生的Binlog完全一样,运维中带来了不少好处和方便。
这个阶段,与上面的几个在流程上不太一样,这个阶段是从节点做的事情,从节点只包括两个阶段,即写集验证和写集APPLY,写集APPLY的并发控制与参数wsrep_slave_threads有关,本身在验证之后,确定相互的依赖关系,如果确定没有关系,就可以并行,而并行度就是参数wsrep_slave_threads的事情了。wsrep_slave_threads可以参照参数wsrep_cert_deps_distance来设置。
流量控制
在PXC中,有一个参数叫fc_limit,它的作用是什么呢?如果一套集群中,某个节点,或者某几个节点的硬件资源较差,或由于节点压力大,导致复制效率低下等各种原因,其结果是:从节点APPLY时,速度非常慢,也就是说,主库在1秒钟之内做的操作,从库有可能会用2秒才能完成,那么这种情况下,就会导致从节点执行任务的堆积,接收队列的堆积。
假设从节点真的堆积了,那么Galera会让它一直堆积下去吗?这样延迟会越来越严重,Galera Cluster将变成一个主从架构的集群,已经失去了强一致状态的属性,那么很明显,Galera是不会让这种事情发生的。此时就说回到开头提到的参数gcs.fc_limit,这个参数在MySQL参数wsrep_provider_options中配置,是Galera的一个参数集合。有关于Flow Control的,还包括gcs.fc_factor,这两个参数的意义是,当从节点堆积的事务数量超过gcs.fc_limit的值时,从节点发起一个Flow Control,而当从节点堆积的事务数小于gcs.fc_limit * gcs.fc_factor时,发起Flow Control的从节点再发起一个解除的消息,让整个集群再恢复。
但我们关心的是如何解决,下面有几个一般所采用的方法:
有很多坑?
有很多同学,在使用过Galera Cluster之后,发现很多问题,最大的比如DDL执行、大事务等,导致服务不友好,这也是很多人放弃的原因。
DDL执行卡死传说:在Galera Cluster中执行一个大的改表操作,会导致整个集群在一段时间内完全写入不了任何事务,都卡死在那里。这个情况确实很严重,导致线上完全不可服务,原因还是并发控制,因为提交操作设置为串行,DDL执行是一个提交过程,那么串行执行改表,当然执行多久,就卡多久,直到改表执行完,其它事务也就可以继续操作了。这个问题现在没办法解决,但我们长期使用下来发现,小表可以这样直接操作,大一点或者更大的,都是通过OSC(pt-online-schema-change)来做。
挡我者死:由于Galera Cluster在执行DDL时,是Total Ordered Isolation(wsrep_OSU_method=TOI)的,所以必须要保证每个节点都同时执行,当然对于不是DDL的,也是Total Order的,因为每一个事务都具有同一个GTID值,DDL也不例外。而DDL涉及到的是表锁,MDL锁(Meta Data Lock),只要在执行过程中,遇到了MDL锁的冲突,所有情况下,都是DDL优先,将所有使用到这个对象的事务,统统杀死。不管是读事务,还是写事务,被杀的事务都会报出死锁异常。不过这点现在确实没有办法解决,也无法避免,不过其影响尚能接受,可以先忍忍。
不死之身:继上面的“挡我者死”,如果集群真的被一个DDL卡死了,导致整个集群都动不了,所有的写请求都Hang住了,那么可能会有人想一个妙招——赶紧杀死,直接在每个节点上面输入kill connection_id等类似的操作,那么将报出很不愿意看到的信息:You are not owner of thread connection_id。此时可能有些同学要哭了,不过这种情况下,确实没有什么好的解决方法(其实这个时候,一个故障已经发生,一年的KPI也许已经没有了,就看是否敢下狠手),其一是等DDL执行完成(所有这个数据库上面的业务都处于不可服务状态),否则就将数据库直接Kill掉,快速重启,赶紧恢复一个节点提交线上服务,然后再考虑集群其它节点数据增量的同步等,这也是Galera Cluster中最大的一个坑,需要非常小心。