自从MySQL组复制(Group Replication)跟随5.7.17发布以来,MySQL真正有了成熟的高可用分布式集群方案,摆脱了之前master-slave+第三方工具的伪集群。笔者将整理关于MySQL组复制的系列文章,这是第一篇,简要介绍组复制的来世今生以及相关特性,希望对大家有帮助
简介
在介绍组复制之前,让我们先回顾一下传统的master-slave复制形式,这将有利于我们理解组复制要解决什么问题
传统复制
传统的MySQL复制形式为主从复制,由一个主库(master)和一个或多个从库(slave)组成。事务只能在主库执行,主库执行事务,提交(commit)之后,将会传播给从库执行。一般这个过程是异步的,由binlog提供基于语句的复制(实际执行的sql),或者基于行的复制(增删改的行sql)。这是一个shared-nothing的系统,所有主从都有一套完整独立的数据。
由于纯异步复制可能会造成数据丢失(以后详细分析),5.5中以插件形式引入了半同步复制,在主库向客户端返回事务已提交的信号之前,多了一个同步步骤,要等从库通知主库事务已接收。这个同步可以发生在server层sync binlog之后、innodb引擎commit之前(after sync),或者在innodb引擎commit之后、返回给客户端之前(after commit)。
组复制
组复制是一种可以用来实现高可用集群的技术,组内的各个服务器相互通信协作,来保证事务的ACID特性。组复制实现了多主写入的特性,即,任意一个主库都可以做数据的更新,事务会被复制到其余的主库以及从库中。在被写入的主库返回给客户端事务已提交之前,组复制插件会保证将被写入的数据,以及写集(write set,写入数据的唯一标识符,一般为主键)有序传播到其他的主库中。注意,这个“有序”非常重要,它将保证所有主库接收到的都是一致的事务,而不至于发生数据错乱。
显而易见的是,多主写入的特性下,多个客户端并发更新数据,必然导致事务冲突,如何解决冲突就显得尤为重要。这个时候,写集(write set)的作用就体现出来了,如果在不同主库上的两个事务,更新了同一行数据,它们将产生相同的写集,MySQL就能检测到事务冲突。MySQL解决冲突的策略是,谁先提交以谁为主,后提交的事务回滚,这样来保证数据的一致性。
和传统复制一样,组复制也是一个shared-nothing的系统,所有主库都有同等的完整数据。
组复制不解决数据分片问题。
组复制细节
组复制整个逻辑是实现在MySQL插件里的,作为传统过复制框架的一种扩展,基于binlog、GTID,和一个第三方的组内通讯组件Corosync by corosync实现。除此之外,组复制还实现了动态变更机制,新机器可以动态加入一个一存在的组复制集内。
组:复制的基本单位
在组复制中,相互复制的几组服务器组成了一个基本单位:组(group)。一个组用uuid作为组名,新的服务器可以自动加入到一个组中,以传统的复制方式复制数据,从而和现有服务器的数据保持一致,无需人工干预。
当一个服务器从组中下线时,其他服务器会自动感知这个下线事件,并作出相应调整
多点写入
由于不存在传统意义上的主库,组中的任意一个节点都可以用来执行事务,包括写入类事务。就像前面提到过的,在事务提交前,要做一些额外的检查工作:
检查事务是否有冲突
将事务传播给其他节点,其他节点需要确认已接收到事务
解决冲突的时候,组复制遵循先到先的的原则。例如,t1/t2两个事务,逻辑上t1先提交,然而在t2的节点上,t2被先执行(本地事务)。当t1事务被广播到t2后,t2所在的节点检测到两个事务写集(write set)冲突,并且t1先提交。那么根据原则,t2所在的节点将回滚t2事务,执行t1。
实践中,如果频繁发生本地事务被远程事务回滚,那么建议将两个事务放同一个节点执行,由本地的锁管理器控制并发问题,而不是等事务传播过来之后,再解决冲突。
监控
和其他指标一样,组复制的监控数据可以从performance_schema中获取,包括组的节点信息、冲突统计、服务状态等。虽然组复制是一个分布式架构,但是监控数据可以从任意一个mysql节点中获取。
例如,要想获取组中的节点信息,可以做如下查询
member1> SELECT * FROM performance_schema.replication_group_members\G
*************************** 1. row ***************************
CHANNEL_NAME: group_replication_applier
MEMBER_ID: 597dbb72-3e2c-11e4-9d9d-ecf4bb227f3b
MEMBER_HOST: nightfury
MEMBER_PORT: 13000
MEMBER_STATE: ONLINE
*************************** 2. row ***************************
CHANNEL_NAME: group_replication_applier
MEMBER_ID: 59efb8a1-3e2c-11e4-9d9d-ecf4bb227f3b
MEMBER_HOST: nightfury
MEMBER_PORT: 13001
MEMBER_STATE: ONLINE
*************************** 3. row ***************************
CHANNEL_NAME: group_replication_applier
MEMBER_ID: 5a706f6b-3e2c-11e4-9d9d-ecf4bb227f3b
MEMBER_HOST: nightfury
MEMBER_PORT: 13002
MEMBER_STATE: RECOVERING
事务信息(等待执行、事务冲突等)
member1> SELECT * FROM performance_schema.replication_group_member_stats\G
*************************** 1. row ***************************
CHANNEL_NAME: group_replication_applier
VIEW_ID: 1428497631:1
MEMBER_ID: e38fdea8-dded-11e4-b211-e8b1fc3848de
COUNT_TRANSACTIONS_IN_QUEUE: 0
COUNT_TRANSACTIONS_CHECKED: 12
COUNT_CONFLICTS_DETECTED: 5
COUNT_TRANSACTIONS_VALIDATING: 6
TRANSACTIONS_COMMITTED_ALL_MEMBERS: 8a84f397-aaa4-18df-89ab-c70aa9823561:1-7
LAST_CONFLICT_FREE_TRANSACTION: 8a84f397-aaa4-18df-89ab-c70aa9823561:7
组复制内部架构
组复制作为一个MySQL插件实现,内部复用了很多现有的代码
插件跟MySQL server层和复制架构层高度集成,利用了现有的binlog、relay log、GTID、从库复制线程、applier等,所以熟悉MySQL的读者能从中发现很多熟悉的东西,没有什么太多的区别。但是于此同时,为了能和其他组件更好的协作,MySQL内部做了一系列重构,模块化抽象等。
桔红色的为插件层,从图上可以看出,最上层为和server层交互的接口,在事务执行的各个阶段都有留下hook,插件能感知事物生命周期的各个阶段。反过来,通过这些接口,插件也能控制MySQL的行为,如什么时候能真正向客户端返回事务已提交。
接口层往下为各具体功能模块。Capture模块用来跟踪记录事物实行的上下文;Applier模块用来应用(执行)远程事务,类似从库的apply线程;Recovery模块用来实现节点的恢复功能,或者帮助新节点和现有节点同步等。
再往下是复制协议层,包括了冲突检测、事务接收、传播等复制相关的逻辑。
最底下是组通信的API层,封装了corosync的一些细节,能更方便的被上层调用。最下面的紫色部分是corosync逻辑,实现了组内通信。
组复制高可用
一般来说,一个能够容错的系统,会利用冗余、复制,将一个节点的状态复制到其他独立节点上,即使其中的某些节点宕机,整个系统依旧可用(虽然可能会有降级,比如性能下降等)
组复制很好的实现了上面提到的特性。故障会被组内通信协议发现并追踪,并隔离到独立节点上。故障恢复协议会被用来自动将节点维持到最新状态。组复制不需要failover,多点写入的特性也保证了节点故障也不会导致写入阻塞。
需要注意的是,虽然数据库服务整体是高可用的状态,但是遇到某个节点宕机,和这个节点通信的客户端需要做重连操作。这个不是组复制本身要考虑的事情,客户端连接池、负载均衡器等都可以用来解决这个问题。
另外,当有新节点加入的时候,可能需要一定的时间才能使新节点和现有节点保持同步,建议全量文件同步加增量复制的方式,这将大大降低同步的时间。
主要翻译自MySQL高可用团队博客[MySQL Group Replication : Hello World! | MySQL High Availability](