一个案例总结 MongoDB 与 Redis 主从同步问题

原创
2023/10/10 15:30
阅读数 844

一个小 Case 总结 MongoDB 与 Redis 的主从同步问题。

作者:徐文梁,爱可生 DBA 成员,一个执着于技术的数据库工程师,主要负责数据库日常运维工作。擅长 MySQL,Redis 及其他常见数据库也有涉猎;喜欢垂钓,看书,看风景,结交新朋友。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 1500 字,预计阅读需要 5 分钟。

问题背景

现场 MongoDB 版本为 4.4.14,PSS 架构。发现一个从节点的状态不正常,一直处于 STARTUP2 的状态,查看 optime 是 1970,因此无法同步数据也无法提供服务。现场同事将另一台从节点的 data 直接暴力 scp 到了不正常的那台,data 中包含的 oplog 也同步过去了,但同步完重启后,不正常的那台还是在 STARTUP2,optime 也还是 1970。

问题分析

了解现场的情况,直接 scp 从节点的 data 会导致数据不一致,这种情况下同步异常是符合预期的,现场数据量不大(20G 不到),因此可以通过逻辑初始化方式进行主从修复。

让现场同事进行如下操作:先 kill 异常从实例,然后把 data 目录清空,最后重新启动实例。做完这些,告诉现场同事只需要耐心等待就可以了,因为数据量不大,理论上一段时间后即可恢复同步正常,但是结局啪啪打脸了,一段时间后现场同事反馈还是同步失败。

此时只能通过查看日志进行解决了,让现场同事提供对应时间段的 mongo 日志,通过查看日志,发现如下信息:

{"t":{"$date":"2023-08-22T19:01:15.574+08:00"},"s":"I",  "c":"INITSYNC", "id":21192,   "ctx":"ReplCoordExtern-10","msg":"Initial sync status and statistics","attr":{"status":"failed","statistics":{"failedInitialSyncAttempts":10,"maxFailedInitialSyncAttempts":10,"initialSyncStart":{"$date":"2023-08-22T09:51:56.710Z"},"totalInitialSyncElapsedMillis":4158864,"initialSyncAttempts":[{"durationMillis":418787,"status":"OplogStartMissing: error fetching oplog during initial sync :: caused by :: Our last optime fetched: { ts: Timestamp(1692698292, 26), t: 44 }. source's GTE: { ts: Timestamp(1692698294, 42), t: 44 }","syncSource":"master:27017","rollBackId":8,"operationsRetried":0,"totalTimeUnreachableMillis":0},{"durationMillis":409866,"status":"OplogStartMissing: error fetching oplog during initial sync :: caused by :: Our last optime fetched: { ts: Timestamp(1692698692, 62), t: 44 }. source's GTE: { ts: Timestamp(1692698709, 12), t: 44 }","syncSource":"master:27017","rollBackId":8,"operationsRetried":0,"totalTimeUnreachableMillis":0},{"durationMillis":429278,"status":"OplogStartMissing: error fetching oplog during initial sync :: caused by :: Our last optime fetched: { ts: Timestamp(1692699126, 26), t: 44 }. source's GTE: { ts: Timestamp(1692699137, 18), t: 44 }","syncSource":"master:27017","rollBackId":8,"operationsRetried":0,"totalTimeUnreachableMillis":0},{"durationMillis":427610,"status":"OplogStartMissing: error fetching oplog during initial sync :: caused by :: Our last optime fetched: { ts: Timestamp(1692699549, 44), t: 44 }. source's GTE: { ts: Timestamp(1692699570, 87), t: 44 }",

通过上面信息,可以定位问题。从库从主库读取 oplog 位点时,主库目前的最小位点大于从库所需要读取的位点,因此导致同步失败,一般这种情况有两种可能:

  1. 全量数据数据量较大,同步时间较长,导致 oplog 之前的位点被覆盖。
  2. 该段时间内的修改操作较多,导致 oplog 之前的位点被覆盖。

问题处理

无论是上面哪种情况,都可以通过调整 oplog 大小进行解决。让现场同事先临时调大一倍 oplog 的值,然后重新进行同步,一段时间后现场同事反馈主从同步已经恢复。实际生产中 oplog 大小的设置参考

问题思考

问题解决了,但是透过这个小案例,我们是否能收获更多呢?答案是肯定的。

思考如下几个问题:

问题一:除了通过逻辑初始化方式进行主从修复,是否还有其他方式呢?

问题二:MongoDB 使用逻辑初始化方式进行主从修复,与 Redis 的主从修复相比有何异同?

问题一

对于问题一,常见的我们可以选择以下三种方式:

  1. 通过关闭异常实例,清空数据目录,然后启动 mongo,mongo 会自动进行初始化,从主实例同步数据,即 case 中使用的方式,该种方式操作简单,最安全,推荐

  2. 对集群中正常节点进行 LVM 快照,然后复制数据到异常实例,启动异常实例。该方式 也不会阻塞源端读写,操作相对第一种方式稍显复杂。

  3. 对集群中正常节点执行 db.fsyncLock() 操作加锁后通过 cp 或 scp 等方式将数据目录拷贝到异常实例,然后对正常实例执行 db.fsyncUnlock() 操作释放锁,然后启动异常实例。该操作会阻塞源端写操作,不推荐。

问题二

对于 MongoDB 使用逻辑初始化方式和 Redis 进行主从修复时,都是分为 全量数据 + 增量同步 两个阶段。

第一阶段

MongoDB 会克隆除 local 数据库之外的所有数据库。为了进行克隆,mongod 扫描每个源数据库中的各个集合,并将所有数据插入到这些集合各自的副本中。当初始化同步完成后,目标成员会从 STARTUP2 状态转为 SECONDARY 状态。

Redis 从实例会连接主实例,发送 psync 或者 sync 命令(不同版本有差别),主实例收到命令后,会通过执行 bgsave 命令生成 RDB 快照文件。

第二阶段

MongoDB 从节点成员在初始化同步之后会获取在数据复制期间新增的 oplog 记录,oplog 是循环写的,如果过小,可能会被覆盖,从而导致同步失败。oplog 大小可以通过 db.getReplicationInfo() 命令查看。

mgset-919:PRIMARY>db .getReplicationInfo()
{
  "logSizeMB" : 512,
  "usedMB" : 0.01,
  "timeDiff" : 228185,
  "timeDiffHours" : 63.38,
  "tFirst" :"Thu Jul 27 2023 16:45:11 GMT+0800(CST)",
  "tLast" :"sun Jul 30 2023 08:08:16 GMT+0800(CST)",
  "now":"Tue Aug 29 2023 16:44:53 GMT+0800 (CST)"
  ……

可通过 db.adminCommand({replSetResizeOplog: 1, size: (200* 1024)}) 命令在线修改,单位为 MB。

Redis 主实例在 bgsave 执行完成后,会向从实例发送快照文件。在此期间写操作命令会记录在缓冲区内,对应的参数是 client-output-buffer-limit,表示大小限制是 512M,持续性限制是当客户端缓冲区大小持续 120 秒超过 128M,则关闭 SLAVE 客户端连接。如果设置过小,也会导致同步失败。可以通过 config get client-output-buffer-limit 命令查看。

#redis-cli -h 127.0.0.1 -p xxxx
127.0.0.1:xxxx> config get client-output-buffer-limit
1) "client-output-buffer-limit"
2) "noraml 524288000 0 0 slave 536870912 134217728 120 pubsub 33554432 8388608 60"

可通过 config set client-output-buffer-limit ”slave 1073741824 268435456 120" 在线设置,单位为比特。

PS:Redis 从 2.8 版本开始支持断点续传。该技术依赖增量复制缓冲区,称为 repl_backlog_buffer,是一个定长的环形数组。如果数组内容写满了,则会从头开始覆盖之前的内容,未被覆盖的情况下,从节点与主节点能够在网络连接断开重连后,只从中断处继续进行复制,而不必重新同步。对应的参数为 repl-backlog-size,如果网络环境较差,可以适当增大该参数值。

问题总结

通过 case,我们将 MongoDB 和 Redis 主从复制原理、修复方式以及常见问题完整的串联起来了,是不是收获不小呢?

留下一个小问题,client-output-buffer-limit 对应的缓冲区和 repl-backlog-size 对应的缓冲区有啥区别呢?

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

爱可生开源社区的 SQLE 是一款面向数据库使用者和管理者,支持多场景审核,支持标准化上线流程,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。

SQLE 获取

类型 地址
版本库 https://github.com/actiontech/sqle
文档 https://actiontech.github.io/sqle-docs/
发布信息 https://github.com/actiontech/sqle/releases
数据审核插件开发文档 https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部