细说 Mongodb、Redis、Mysql 数据同步机制

数据库集群具有数据备份、负载均衡等功能。比如 Mongodb 的复制集模式,Redis、Mysql 的主从模式。这些模式均有它们各自的实现方式。有相同之处也有截然不容的处理方式。

Mongodb

MongoDB 复制集模式数据同步主要分两类:

  1. Initial Sync 初始化同步,可理解为全量同步;
  2. Replication 拉取同步源 oplog 进行重放,可理解为增量同步。

在解释两类模式前先介绍 Mongodb 的 oplog 日志。在 Mongodb 中的 oplog 是一个固定长度(可设置)的集合(存储在 local 数据库里),记录着所有修改数据的操作,当集合满后会删除最旧的数据。并且记录需要具有幂等(idempotent)的特性,也就是能支持执行多次结果是一样的。为实现幂等 mongodb 对很多操作进行来转换,比如将 insert 转换为 upsert、$inc 操作转换为 $set 等等。

Initial Sync

对于一个新加的无数据的、或 oplog “太旧”的从节点会进行初始化同步过程。同步过程大致可以分为以下几个步骤:

  1. 获取同步源节点最新 oplog time 标记为 start;
  2. 全量从同步源节点克隆集合数据到目标节点,并对 _id 建立索引;
  3. 目标节点对数据建立索引;
  4. 获取目标节点最新的 oplog time,标记为 minValid;
  5. 在目标节点执行 start 到minValid时间段的 oplog(注:因为 oplog 支持幂等性,重复执行是没问题的);
  6. 成为正常节点。

Initial Sync 步骤结束后就进入到 Replication 同步模式。

Replication

这个过程大致思路是:Secondary 不断地拉取同步源新生产的 oplog 日志,并且进行重放(Redo)操作。这样就使得该节点和其他节点保持了数据的一致性。当然该 Secondary 也同样会把 oplog 日志记录并维护在本地一份。大多数情况下同步源就是Primary 节点,但 Mongodb 也会更智能地通过 ping 去选择最近的节点来作为同步源。

更细分的步骤可以分这几个步骤:

  1. producer thread 线程(单线程)从同步源上拉取 oplog 日志,并添加保存到 BlockQueue 队列中;
  2. 然后 replBatcher thread这个线程(单线程)从 producer thread 的队列里逐个取出oplog日志,并放到自己维护的队列里;
  3. 最后 sync 线程将 replBatcher thread 的队列分发到默认16个 replWriter 线程(多线程),由 replWriter thread 来最终重放每条 oplog。

Redis

Redis 主从复制也和 Mongodb 一样也有全量和增量同步模式。在介绍流程前先了解下 Redis 中的 RDB 文件。Redis 的一种持久化机制是快照:通过 fork一个子进程,然后执行 BGSAVE 操作将内存中的数据持久化到 RDB 文件中。

全量:

  1. Slave 和 Master 初次建立连接后,Slave 向 Master 发送一个 SYNC 命令;
  2. Master 接收到 SYNC 后,fork 一个子进程执行 BGSAVE 操作生成最新的 RDB 快照文件,并且会将该时间点后的产生写记录下来放在缓冲区中;
  3. 当 BGSAVE 操作完成后,向 Slave 发送整个 RDB 文件;
  4. Slave 接收到 RDB 文件后将其载入进来作为当前的数据,如果有历史数据则删除,注意:载入过程是阻塞式的,此时无法提供服务;
  5. Master 在发送完 RDB 给 Slave 后开始将缓冲区中存储了的写操作同步给 Slave;
  6. Slave 接受到 Master 的缓冲区中写命令后进行执行;

增量:

当全量同步完成后就会进入增量同步阶段。具体流程是:

  1. Master 将每一条写操作均同步给 Slave;
  2. Slave 接收到写命令后开始执行该命令;

当 Master 和 Slave 之间的链接中断后,再链接在2.8版本之前需要再进行一次全量同步,2.8+版本 Redis 对该问题进行了优化,不一定会进行一次全量同步而是部分重同步。大致思路是:在 Master 和 Slave 上都记录着一个复制偏移量(replication offset)和一个主服务器 ID(master run id)。当Slave在断开链接后再重连时会带上 offset 值和 runid,Master 根据这个 runid 与自己记录的 runid 进行比对和判断 Slave 指定的 offset 值在缓冲区里是否能找到来综合考量,如果 runid 一致并且缓冲区里存在 Slave 想要的数据则直接将缓冲区里 Slave 没有的数据同步给 Slave,Slave 接收到该数据后进行执行就好。如果两个条件不都满足则进行一次全量同步。

Mysql

Mysql 主从模式下数据同步过程比较简单。大致是从服务器从主服务器中拉取 binlog 放到本地的中继日志(relay-log)中,然后再由一个从 SQL 线程读取中继日志后重做事件。这里面所谓的 binlog 是一个二进制文件,它将主服务器产生的写操作串行地记录下来。

细分步骤可归纳为:

  1. Master 将写操作记录在二进制文件 binlog 中;
  2. Slave 开启一个 I/O 线程连接上 Master,请求拉取 binlog 日志(可以拉取最新也可以指定拉取时间点);
  3. Master 接收到请求后,通过一个 I/O 线程来处理请求并返回 binlog 等数据信息给 Slave;
  4. Slave 接受到返回的数据后将数据串行地写入到中继日志(relay-log)文件中;
  5. Slave 的另外一个 SQL 线程检测到 relay-log 有新的数据后,立即读取解析新的数据并执行这行写操作(重放)。

参考链接

添加新评论