内容来源: 2017 年 7 月 22 日, UCloud 高级研发工程师王松磊在 "饿了么技术沙龙 [第九弹] 上海研发中心. 运维专场" 进行数据库高可用架构演讲分享. IT 大咖说(微信 id:itdakashuo) 作为独家视频合作方, 经主办方和讲者审阅授权发布.
嘉宾演讲视频及 PPT 回顾: http://suo.im/2obXuQ
摘要
分享 UCloud 在数据库高可用上的最佳实践. 首先介绍 MYSQL 常见的高可用方式, 并分析其存在的问题, 然后给出 UCloud 对此的思考和解决方法.
MySQL 常见的数据同步方式
MySQL 数据同步方式大体分为两大类, 第一类是使用 MySQL 传统的异步或半同步复制协议, 比较典型的有 MHA+MySQL 或者 **Proxy+MySQL. 第二类是使用分布式协议, 这方面有 Group Replication 或 Galera Cluster+MySQL.
原生的异步或半同步复制协议
首先来看下原生的异步或半同步复制协议. 它通过 Master 的 dump 线程与 Slave 的 IO 线程进行交互, 当 binglog 发生更新时, 将 Binlog 传输到 Slave, 实现 Master 和 Slave 的数据同步.
然后还可以通过使用不同的拓扑结构来实现不同的功能, 如 master 和多个 Slave 做数据同步, 如多个 master 相互搭建复制. 目前绝大多数的 Proxy 产品都是使用 MySQL 的原生复制作为数据同步方式.
分布式协议做数据同步
这种是近几年新兴的同步方式, 它的分布式算法是通过选举的方式, 解决在分布式的系统中如何就某一决议达成一致的问题.
使用分布式协力做数据同步的方式, 在近两年出现了一系列开源或闭源的产品, 如腾讯的 phxsql,perconna 和 mariadbde galera 插件, 社区版的 Group,Tidb 等. 分布式协议通常需要至少三节点的 MySQL, 相比于原生的 MySQL 复制提供了更可靠的保证数据一致性的方法, 但是由于技术教新(相比于 MySQL 复制), 暂时并没有替代原生复制成为成为绝大多数 MySQL 数据库同步方式.
MySQL 双节点架构
经典的双节点架构有一个 VIP 接入某一个 Proxy, 然后下端接一主一从两个节点, 从节点做一个数据总集的节点, 以防止当主节点挂掉后, 服务仍然可以使用, 当然也可以作为备份来用.
这种架构使用 binglog 这种二进制日志的方式来做主从同步, 采用半同步进行复制, Proxy 同一时间只接入有一个节点, 另外还可以根据需求选择是否使用 GTID 或进行拓扑结构的扩展.
当发生异常情况, 例如 Master 发生宕机后, Proxy 会将业务切换到 Slave, 宕机恢复后, 再将业务回切并进行数据回补, 或者使用恢复后的 Master 作为新的 Slave, 重新搭建复制.
MySQL 复制常见问题
上图时序图可以从中间分开, 左边是主节点, 右边是从节点. 主库任何的事务结束后都会同步到从库, 保证数据的一致性.
退化为异步复制
主库在发送 binglog 的时候会等待从库的应答, 而没有接受到应答就会出现超时问题. 这就会造成下一个事务到达的时候主库就不会再应答了, 也就我们说的退化, 从半同步复制退化到异步复制. 退化之后数据的一致性就会得不到保证.
退化的复制是可以恢复为半同步复制的. 每个事务提交时, 会在半同步插件记录当前记录的 binglog 的文件名和位置. IO 线程在记录 relay log 完成后, 会将 relay log 对应的主库的 binglog 的文件和位置发送给. Dump 线程在接受应答后, 会对比 Slave 发送的应答和半同步插件记录的内容, 如果 Slave 发送的文件和位置要大于等于半同步插件中记录的内容, 那么恢复半同步复制.
发生意外宕机
从写入 binglog 到通知 Dump 线程阶段如果发生意外宕机就会造成主库和从库数据不一致.
这种不一致只是在 master 完成了, 但是没来得及复制 slave 的数据库操作. 这些操作在业务看来是执行失败的数据库操作. 但是在主库宕机恢复后, 这些数据库操作会被 recovery 机制作为成功的数据库操作来处理, 同时 binglog 是存在的, 但是并没有复制到 slave.
如果发生了业务切换, 继续在 slave 执行数据库操作, 那么在一些特性的场景下, 如果 master 当即回复, 可能造成复制失败的情况.
非 auto_position 的 Master.info
Master.info 记录的 IO 线程复制的相关信息, 记录的信息与 show slave status 显示的 IO 线程相关含义相同. 用于在 MySQL 启动时, 装载复制 IO 线程的相关信息, 保证重启后复制仍能继续进行.
对于非 auto_position 的 Master.info 在 change master 时, 会记录主机的 IP, 端口, 用户名等信息到 master.info 中, IO 线程在记录 relay log 后更新 master.info 中的记录 master 的文件和位置.
当意外宕机的时候, 可能发生记录了 relay log 但是没有更新 master.info 的情况.
非 GTID 的 Relay-log.info
Relay-log.info 记录着 SQL 线程复制相关信息, 记录的信息与 show slave status 显示的 SQL 线程相关信息含义相同. 用于在 MySQL 启动时装载复制的相关信息, 保证重启后复制仍能够继续进行.
当发生宕机, 复制重新启动后, 会存在 relay-log.info 中记录的信息要晚于真正执行 relay log 的情况. SQL 线程启动时, 可能读取到已经执行过的 relay.log.
这是如果开启了 GTID, 重复的 GTID 会被过滤, 而没有开启, 发生重复执行的情况, 可能导致复制错误.
重复 GTID 的忽略
GTID 为全局唯一标示符, 与事务一一对应的, master 的事务对应的 GTID 不会因为复制到 Slave 而发生改变, 级 master 的事务复制到 slave, 被 sql 重现后, 记入 Slave bining 中的事务对应的 GTID 仍然为 master 的 gtid.
相同的 GTID 对应的事务不会被重复复制到 Slave,slave 对于执行过的 gtid 也不会重复执行.
在重做主从或者误操作的情况下, 由于 GTID 不会重复执行, 所以可能会导致 master 和 slave 的数据差异.
复制问题的解决方法
退化为异步复制的解决方案
异步复制阶段的宕机问题是主要因素, 解决这一问题的主要思路是减少异步复制的存在时间.
可以采用增加半同步的超时时间, 牺牲一定的可用性来保障数据的一致性. 也可以通过增加新复制通道, 只记录文件和位置, 并且不退化, 只重连, 保证复制正常的情况下一直存在一条半同步复制. 增加异步和同步共存的复制方式也是一个方案.
发生意外宕机解决方案
针对这一问题, 应该避免进行重复的操作, 以及在 MySQL-5.6 以前的版本使用自增 ID. 对于 recovery 机制进行优化, 通过配置或者其他方式连接原 slave, 读取 master 宕机时的复制进度. 记录 binglog 的树屋, 如果没有同步到 Slave, 仍然事务回滚, 回滚后对 binglog 做 truncate 处理, 另外还需对删除的 binglog 做日志记录.
Master.info 和 Relay_log.log
这里主要是解决宕机恢复后复制起点问题. 建议尽量使用 GTID 作为复制的依据, 取代较早的文件和位置, slave 宕机恢复后, 对 relay log,binglog 和当前复制的进度做较完善的校检.
重复 GTID 的忽略
这方面大多是人为造成的. 所以建议在重做主从后, 做完整的复制进度检查, 增加简单的审计表, 对敏感的操作做记录, 如 reset master,change master 等, 并对比 master 和 slave 的敏感操作记录.
来源: https://juejin.im/post/5b0915096fb9a07ab83e79cf