实现主备一致性的方式:使用 Binlog 日志
备库处于 readonly 的优点: 1.避免误操作; 2.避免双 M 导致不一致; 3.基于 readonly 区分主备库。

主备同步的流程: 1.备库配置主库的连接方式、binlog 文件名、日志偏移量
2.start slave 命令启动两个线程,一个 io 线程进行连接,拉取 binlog 放到 relaylog 中,一个 sql 线程执行 relaylog 重放 binlog

主备同步的时候,会设计到 Binlog 存储格式 的问题。
其中需要注意,mixed 格式下,now() 采用 statement 格式,但是前面会有个 SET TIMESTAMP 的语句。

双 M 结构可能存在循环复制的原因是:
A 库将 binlog 发给 B 库,B 库重放 binlog 后,将 sql 又发给 A 库。

当双 M 结构时,如何避免循环复制?
1.binlog 每一条语句有一个 server id; 2.备库重放 binlog 的时候,生成 server id 相同的 binlog。 3.只重放非自己 server id 的语句;

当然也存在不正当使用数据库导致的循环复制的情况。

Releted:24.MySQL保证主备一致性24.MySQL是怎么保证主备一致的?.pdf

主备同步的方式

MySQL 基于 Binlog 日志 来进行主备库的数据同步。

建议将备库设置为 readonly 模式:

  • 避免运营类的查询语句在使用备库时,误操作。
  • 防止切换逻辑有 bug,导致出现双写,进而主备不一致。
  • 基于 readonly 状态区分主备库。

readonly 权限对于 super 权限用户是无效的,而同步更新的线程就是 super 权限。

24.MySQL保证主备一致性-1.png|600

主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写 binlog。

备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的:

  1. 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
  2. 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。
  3. 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。
  4. 备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
  5. sql_thread 读取中转日志,解析出日志里的命令,并执行。

后来由于多线程复制方案的引入,sql_thread 演化成为了多个线程。

binlog 存储格式问题

statement、mixed、row 格式。

24.MySQL是怎么保证主备一致的?.pdf 中有查看 binlog 日志的内容。

循环复制问题

实际生产上使用比较多的是双 M 结构,如下是主备切换流畅:

24.MySQL保证主备一致性-2.png|600

双 M 结构和 M-S 结构,其实区别只是多了一条线,即:节点 A 和 B 之间总是互为主备关系。这样在切换的时候就不用再修改主备关系。

双 M 结构还有一个问题需要解决。业务逻辑在节点 A 上更新了一条语句,然后再把生成的 binlog 发给节点 B,节点 B 执行完这条更新语句后也会生成 binlog。(我建议你把参数 log_slave_updates 设置为 on,表示备库执行 relay log 后生成 binlog)。

MySQL 在 binlog 中记录了这个命令第一次执行时所在实例的 server id。因此,我们可以用下面的逻辑,来解决两个节点间的循环复制的问题:

  1. 规定两个库的 server id 必须不同,如果相同,则它们之间不能设定为主备关系;

  2. 一个备库接到 binlog 并在重放的过程中,生成与原 binlog 的 server id 相同的新的 binlog;

  3. 每个库在收到从自己的主库发过来的日志后,先判断 server id,如果跟自己的相同,表示这个日志是自己生成的,就直接丢弃这个日志。

不正当使用导致的循环复制

在上面双 M 下,依然可能存在循环复制的问题,是一些误操作导致的情况。

  1. 当主库更新事务后,对自己的 server_id 进行了更新,导致日志重传回来后,server_id 不一致导致再执行。

    set global server_id=x;
  2. 三个节点,依赖关系为 A B,B > C。那么当事务在 A 节点上执行时,B 和 C 之间存在循环复制。因为 server_id 是 A 的 id。

    # 解决办法
    stop slave;
    CHANGE MASTER TO IGNORE_SERVER_IDS = (server_id_of_B);
    start slave;
     
    # 过一段时间后,再更改回来
    stop slave;
    CHANGE MASTER TO IGNORE_SERVER_IDS = ();
    start slave;