开源地址:https://github.com/tencent-wechat/phxsql
PhxSQL 是一个兼容 MySQL、服务高可用、数据强一致的关系型数据库集群。PhxSQL 以单 Master 多 Slave 方式部署,在集群内超过一半机器存活的情况下,可自身实现自动 Master 切换,且保证数据一致性。
PhxSQL 基于 Percona 5.6 开发。Percona 是 MySQL 的一个分支,功能和实现与 MySQL 基本一致。因此本文后续直接把 MySQL 作为讨论对象。
MySQL 半同步复制存在缺陷,在 Master 进行切换的场景下,数据难以保证一致。
- 当旧 Master 复制失败时,旧 Master 和 Updated Slave(已收到 Binlog 的 Slave)需要回滚数据。
- 当 Master 进行切换时,旧 Master 仍有部分 Client 进行读写。
关于 MySQL 半同步复制的数据一致性问题可查看微信后台团队公众号文章 MySQL 半同步复制的数据一致性探讨。
PhxSQL 的设计是为了解决 MySQL 半同步复制的不足,使 MySQL 集群在 Master 切换过程中保证数据的一致。
PhxSQL 架构
为了解决 MySQL 的两个问题(Binlog 复制和 Master 切换),PhxSQL 设计了两个模块(Phxbinlogsvr、Phxsqlproxy)和一个 MySQL 插件(Phxsync)。Phxbinlogsvr 负责处理 MySQL 的 Binlog 复制和 Master 管理;Phxsqlproxy 负责透传 Client 请求到 Master;Phxsync 插件负责 MySQL 和 Phxbinlogsvr 的交互。 一台部署了 Phxsqlproxy,MySQL 和 Phxbinlogsvr 的机器称为 PhxSQL Node。如图1。
PhxSQL 复制流程
图 2 MySQL 和 PhxSQL 的数据复制流程
在 PhxSQL 中,Phxbinlogsvr 负责管理 MySQL 的角色和存储 MySQL 的 Binlog,Phxbinlogsvr 和其管理的 MySQL 部署在同一台物理机上。
MySQL Master 在 Send Event 阶段不再把 Binlog 复制给 Slave,而是通过 Phxsync 插件,把数据复制到 Phxbinlogsvr 集群。
MySQL Slave 也不再从 Master 获取 Binlog,而是从本机的 Phxbinlogsvr 获取。
Phxbinlogsvr 集群使用 Paxos 协议进行数据复制。
PhxSQL 使用 PhxPaxos 库,详情请查看微信后台团队公众号文章微信自研生产级 paxos 类库 PhxPaxos 实现原理介绍。
从逻辑上来看,利用 Paxos 协议进行复制,使 Phxbinlogsvr 形成一个可靠的日志存储。PhxSQL 可以看成是为 MySQL 增加了一个用 Paxos 实现的可靠 Binlog 存储,只要集群中多数派机器存活,就可以解决半同步复制的回滚问题。如图3。
分别从 Master 和 Slave 的角度来解释:
- Master 重启时,通过询问 Phxbinlogsvr(多数派)Pending Binlog 是否存在来决定是否需要回滚。如图4。
- Slave 从本机 Phxbinlogsvr 能拉取到的 Binlog 都已经经过 Paxos 协议成功复制到多数派机器,因此对于 Slave 来说不存在回滚的问题。
Phxbinlogsvr 通过 Paxos 协议复制数据,很好的解决了 MySQL 中需要手动回滚 Binlog 和在大集群时同时需要回滚 Updated Slave 上的 Binlog 的问题。
PhxSQL 的 Master 管理
MySQL 多 Master 同时写入会导致数据的不一致。如图5,机器A是旧 Master,在收到机器B成为了新 Master 的消息之前提交了 Transaction 3;而同时机器B已成为新 Master,Transaction 3 则会留在机器A而未复制到机器B,最终两机的数据不一致。
MySQL 多 Master 问题的产生,源于机器间无法得知当前 Master 的状态,最后导致两台机器的数据不一致。
即使使用外部服务(例如 zookeeper)也无法解根本问题。
- 对 Master 查询和查询之后的操作不是原子操作,无法保证操作时的准确状态(例如机器A向外部服务查询得知自己是 Master,然后执行复制 Binlog 操作。但期间出现故障导致两个操作之间停顿了很长时间(譬如 1 天)。在该期间内 Master 被切换,使得机器A在执行复制 Binlog 时,已不再是 Master,导致了多 Master 的情况发生。)
- Master 管理依赖外部服务的稳定性。
多 Master 问题由于细节太多,暂不在此讨论。
PhxSQL 自身进行了 Master 管理,具有以下特点:
- Master 通过 Paxos 协议投票选出。
- Master 带有租约,并定时续租。租约过期后,需重新选举新的 Master。
- 全局只有 1 个 Master,或者没有 Master 存在。
- 有效拒绝过期 Master 的非法写入。
PhxSQL 的 Master 自动切换
PhxSQL 实现了旧 Master 的自动数据回滚和 Master 管理,使得 PhxSQL 可以安全地实现 Master 的自动切换,提供高可用服务。和常见的 MySQL 切换 Master 方案不同,PhxSQL 在切换 Master 之后仍然保证集群内各机数据一致。
PhxSQL 自动 Master 流程如下:
- Slave 机器上的 Phxbinlogsvr 定期检查 Master 是否过期。如果过期转第 2 步,否则继续第 1 步;
- Phxbinlogsvr 检查本机 MySQL 是否已执行完所有 Binlog。如果已完成转第 3 步,否则继续第 1 步;
- Phxbinlogsvr 发起投票选举新的 Master。如果投票成功,提升本机 MySQL 为 Master,关闭 readonly 开关;否则继续第 1 步;
- 旧 Master 恢复,本机的 Phxbinlogsvr 查询发现已不是 Master,切换 MySQL 角色为 Slave,设置从本机 Phxbinlogsvr 拉取 Binlog,并开启 readonly 开关。
Phxsqlproxy 请求透传
Phxbinlogsvr 解决了多 Master 同时写入的问题,使得 MySQLClient 向旧 Master 写入数据会产生失败。虽然保证了数据的一致性,但仍存在下面 2 个问题:
- MySQLClient 持续向旧 Master 写入数据,从而持续的失败。(服务不可用)
- 部分 MySQLClient 向新 Master 写入数据,但其他 MySQLClient 仍然向旧 Master 读取数据,导致读不到最新的数据。
上述两个问题都是由于 MySQLClient 的 Master 信息更新不及时;部分 Client 没有及时更新,使得有可能产生 PhantomRead (两次读的结果不一致)。
若 Slave 机器被访问,Phxsqlproxy 则会把请求透传到 Master 机器的 Phxsqlproxy。由于 PhxSQL Master 的全局唯一性,保证了只存在一台 MySQL 被访问。从而解决了多台机器同时被读写的问题。
PhxSQL 性能
使用 sysbench 工具对 PhxSQL 和 MySQL 的半同步复制进行了性能对比。PhxSQL 因为增加了 Phxsqlproxy,导致读性能比原生 MySQL 略低;但由于 PhxPaxos 的实现比 MySQL 的半同步更加高效,让 PhxSQL 的写性能比半同步复制更好。
PhxSQL 比 MySQL 读性能比原生 MySQL 略低,但写性能比 MySQL 半同步复制更好。
读性能 | 写性能 | |||
Client 线程数 | QPS | 耗时 | QPS | 耗时 |
200 | 约降低3% | 耗时约增加2% | 约增高 25% | 约降低 20% |
500 | 约降低 13% | 约增加 10% | 约增高 16% | 约降低 10% |
测试环境和结果如下:
机型信息
CPU : Intel (R) Xeon (R) CPU E5-2420 0 @ 1.90GHz * 24
内存 : 32G
磁盘 : SSD Raid10
网络互 Ping 耗时
Master -> Slave : 3 ~ 4ms
Client -> Master : 4ms
压测工具和参数
sysbench --oltp-tables-count=10 --oltp-table-size=1000000 --num-threads=500 --max-requests=100000 --report-interval=1 --max-time=200
压测内容
PhxSQL 和半同步复制在 Client 线程 200 和 500 的环境下进行下面方式的压测:
- insert.lua (100% 写)
- select.lua (0% 写)
- OLTP.lua (20% 写)
压测结果
Client 线程数:200
insert.lua (100% 写) | ||
QPS | 耗时 | |
PhxSQL | 5076 | 39. 34/56.93 |
MySQL
半同步 |
4055 | 49. 27/66.64 |
select.lua (0% 写) | ||
QPS | 耗时 | |
PhxSQL | 46334 | 4. 21/5.12 |
MySQL
半同步 |
47528 | 4. 10/5.00 |
OLTP.lua (20% 写) | ||
QPS | 耗时 | |
PhxSQL | 25657 | 140. 16/186.39 |
MySQL
半同步 |
20391 | 176. 39/226.76 |
Client 线程数:500
insert.lua (100% 写) | ||
QPS | 耗时 | |
PhxSQL | 8260 | 60. 41/83.14 |
MySQL
半同步 |
7072 | 70. 60/91.72 |
-
select.lua (0% 写) | ||
QPS | 耗时 | |
PhxSQL | 105928 | 4. 58/5.81 |
MySQL
半同步 |
121535 | 4. 17/5.08 |
-
OLTP.lua (20% 写) | ||
QPS | 耗时 | |
PhxSQL | 46543 | 192. 93/242.85 |
MySQL
半同步 |
33229 | 270. 38/345.84 |
注:耗时分别为测试结果的平均耗时/95% 分位数耗时,单位 ms
总结
PhxSQL 解决了 MySQL 半同步复制中数据回滚和多 Master 的问题,使其能实现自动 Master 切换且保证数据一致。PhxSQL 因为增加了 Phxsqlproxy,导致读性能比原生 MySQL 略低;但由于 PhxPaxos 的实现比 MySQL 的半同步更加高效,让 PhxSQL 的写性能比半同步复制更好。
开源地址:https://github.com/tencent-wechat/phxsql
附录:
微信自研生产级 paxos 类库 PhxPaxos 实现原理介绍