2.2 性能提升
MySQL一直致力于提升半同步复制的性能,从以下三个方面便可见:
(1)支持发送二进制日志事件和接收ACK的异步化。
(2)控制主库接收确认从库的反馈数量。
(3)二进制日志互斥锁的改进。
2.2.1 ACK异步化
旧版本的半同步复制受限于Binlog Dump线程,原因是该线程承担了两个不同且又十分频繁的任务:传送二进制日志事件给从库;接收从库的ACK反馈信息。这两个任务是串行的,Binlog Dump线程必须等待从库返回之后才会传送下一个事件。Binlog Dump线程已然成为整个半同步复制性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的吞吐量。单个Binlog Dump线程发送和接收的工作流程如图2-1所示。
图2-1 单个Binlog Dump线程发送和接收的工作流程
为了解决上述问题,在MySQL 5.7.4版本的半同步复制框架中,独立出一个Ack Receiver线程,专门用于接收从库返回的ACK请求,这将之前Binlog Dump线程的发送和接收工作分为了两个线程来处理。这样主库上有两个线程独立工作,可以同时发送二进制日志事件到从库,和接收从库的ACK信息。因此半同步复制得到了极大的性能提升。Binlog Dump线程与Ack Receiver线程的工作流程如图2-2所示。
图2-2 Binlog Dump线程与Ack Receiver线程的工作流程
Ack Receiver线程在主库启用半同步复制时创建,并在主库禁用半同步复制时销毁,它是自动创建和销毁的,因此不受用户控制。它的状态信息可以从performance_schema中查询到:
Ack receiver线程有以下三个状态:
- Waiting for semi-sync slave connection
- Waiting for semi-sync ACK from slave
- Reading semi-sync ACK from slave
在MySQL 5.7.17之前,这个Ack Receiver线程采用了select机制来监听从库返回的结果,然而select机制监控的文件句柄只能是0~1024,当超过1024时,用户在MySQL的错误日志中会收到类似如下的报错,更有甚者会导致MySQL发生宕机。
semi-sync master failed on net_flush() before waiting for slave reply.
从MySQL 5.7.17版本开始,官方修复了这个bug,开始使用poll机制来替换原来的select机制,从而可以避免上面的问题。其实poll调用本质上和select没有区别,只是I/O句柄数理论上没有了上限,因为它是基于链表来存储的。
2.2.2 控制从库反馈的数量
MySQL 5.7新增了rpl_semi_sync_master_wait_for_slave_count系统变量,可以用来控制主库接收多少个从库写事务成功后的反馈,给高可用架构切换提供了灵活性。如图2-3所示,当该变量值为2时,主库需等待两个从库的ACK。
图2-3 “一主两从”的半同步复制
使用这个功能,可以在不同机房部署主服务器和两个从服务器,并配置半同步复制以将事务复制到至少两个从库,以便在多个服务器一次性崩溃的情况下减少数据丢失的可能,从库越多,数据越安全。
2.2.3 二进制日志互斥锁的改进
旧版本半同步复制在主库提交二进制日志的写会话和Binlog Dump线程读取二进制日志的操作时,都会对二进制日志添加binlog lock互斥锁,用于保护二进制日志的读写安全。使用此互斥锁,二进制日志读写操作是安全的,但会导致二进制日志文件的读写串行化。不仅Binlog Dump线程和用户会话不能同时读写二进制日志,就连多个Binlog Dump线程本身也无法同时读写。每当一个会话正在读取或写入二进制日志文件时,所有其他会话都必须等待。如此顺序读写是一个瓶颈,尤其是当读写操作很慢时。串行化读写二进制日志如图2-4所示。
图2-4 串行化读写二进制日志
MySQL 5.7.2对binlog lock进行了以下两方面的优化:
- 从Binlog Dump线程中移除binlog lock。
- 加入了安全边际以保证二进制日志的读安全。
二进制日志文件是一个仅用于追加二进制事件的日志文件,可以安全地从中读取没有锁定的二进制事件,因此可以从Binlog Dump线程中删除binlog锁。不使用binlog锁,而是为活动binlog维护安全读取边界(最大位置)。Binlog Dump线程永远不会读取超过安全读取的边界。当到达边界时,它将等待边界更新。用户会话负责在追加了二进制事件后更新安全读取边界。改进后的二进制日志读写如图2-5所示。
图2-5 改进后的二进制日志读写
从图2-5中一目了然:
- 读取二进制日志事件时,Binlog Dump线程不会相互阻塞。
- 正在写二进制日志事件的用户会话不会阻止Binlog Dump线程。
- 读取二进制日志事件的Binlog Dump不会阻塞用户会话。
因此,Binlog Dump线程和用户会话都可以获得更好的吞吐量,尤其是在有很多从库时,这种改进非常显著。