开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第十六天,点击查看活动详情
📣 大家好,我是Zhan,一名个人练习时长一年半的大二后台练习生🏀
📣 这篇文章是复习 Redis 主从复制同步数据原理 的学习笔记📙
📣 如果有不对的地方,欢迎各位指正🙏🏼
📣 与君同舟渡,达岸各自归🌊
👉引言
Redis的特性就是必须支撑读高并发的,单节点Redis并发能力虽然不错,但是在高并发场景下也顶不住,且单节点如果宕机了,就会导致服务不可用。因此就有了主从这样的架构模式,给master去写,数据同步给它的小弟slave去读(读写分离),这样请求就可以分发在多个Redis结点上了,同时也方便了水平扩容:
所有的数据修改只发生在主服务器上,然后将最新的数据同步给从服务器上,以保证主从服务器的数据是一致,当然这只是我们从整体上来看,具体是如何做到主从数据一致性的我们下面来看看:
🛢 全量同步
当我们进行第一次同步,例如:服务器A和服务器B两个服务器,在服务器B执行:
replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>
那么此时服务器B就是服务器A的从结点了,那么服务器A就需要把数据同步给服务器B,由于是第一次同步,因此是全量同步,也就是把所有的数据都传给服务器B
那么服务器A如何判断是第一次同步呢?
在执行replicaof命令后,从服务器B就会给主服务器A发送psync指令,表明要进行数据同步,而psync命令有两个参数:
- Replication Id:数据集的标记,主从结点的数据集一致,也就是说
replication id相等 - offset:偏移量,表示复制的进度,如果从节点的
offset落后于主节点的,就代表从节点的数据落后于主节点,需要数据更新,通过主从结点的偏移量的差值也能知道需要更新哪一段数据
对于第一次同步的服务器B来说,并没有主服务器的replication id,且没有进行过复制,因此发送的命令为:psync ? -1
主服务器会根据从服务器发送的命令来判断下一步的操作,针对于psync ? -1,主服务器会返回FULLRESYNC {replication id} {offset}:
- FULLRESYNC:表明此次同步为全量同步
- Replication Id:主结点的数据集,这样主节点和从节点的数据集Id就一致了
- offset:表明此次从节点复制的数据的偏移量
上述的操作可以用下图来表示:
在建立完连接并协商好要传输的数据之后,就要开始正式传输数据了:
主服务器会执行bgsave在后台生成RDB快照,然后传输给从节点,从节点拿到数据后,清空当前结点的所有数据,然后导入RDB文件:
在服务器B导入 RDB 快照期间,客户端对服务器A的写操作,这部分数据服务器B并没有,那么Redis怎么解决这里的主从不一致呢?
主服务器会把:主服务器生成RDB文件期间、主服务器发送RDB文件期间、从服务器加载RDB文件期间,三个时间段的数据更改写入replication buffer缓冲区中
然后在从服务器RDB文件加载结束后,会发送一个确认的消息给主服务器,主服务器就会把缓冲区里面所有记录的写操作发送给从服务器,从服务器执行缓冲区中的操作,便主从数据一致了。
至此,第一次全量同步结束,完成第一次同步之后,主从结点之间就会建立一个TCP连接,后续的写操作的命令传播就靠这个长连接的TCP连接
🎃 增量同步
在第一次同步之后,主从结点通过
TCP连接来进行通信写命令,可是由于网络波动,TCP连接断开了,重连后,中间的那部分数据会不会丢失呢?
如果说重连之后,要进行一次全量复制重新建立TCP连接,那么这个开销太大了,这就要提到增量同步。我们在讲psync命令的时候提到了两个个变量offset,在重连的时候,psync命令中的replication id是已知的,因此完整的命令就是:
fsync <replication id> <offset>
然后主服务器发现replication id相同,offset大于从服务器,因此发送给从服务器CONTINUE,表明接下来使用增量同步
offset 和 数据的关系记录在哪呢?
主服务器进行命令传播的时候,不仅会把写命令发送给从服务器,还会把命令写到缓冲区repl_backlog_buffer中,因此这个缓冲区中会保存最近传播的写命令。
很明显,我们不可能缓冲所有的写命令,毕竟空间有限,也就是说只会缓存一定量的写命令,这是通过环形缓冲区来实现的:
也就是说,当缓冲区写满后,最旧的数据会被最新的数据所覆盖,那么就存在这个风险:网络恢复的时候,从服务器想读的数据已经被覆盖了,此时主服务器就会不得已采用全量同步,因此我们最好把缓冲区的大小设置的大一些
综上,全量同步的大概流程就是:
- 从服务器发送replication id 和 offset
- 主服务器根据这两个判断是否进行增量同步:
- replication id:首先判断是否是同一个数据集
- offset:
- 如果说从服务器想读的数据已经被覆盖了,那就进行全量同步
- 如果数据还在,就把offset差值的数据传给从服务器,在此期间replication buffer缓冲区也会记录在此期间主服务器进行的写操作命令,在增量同步结束之前把replication buffer中的命令发送给从服务器
🔗 主-从-从链式结构
在前面的分析中我们可以发现,主节点在主从同步的时候所做的比较耗时的两个操作就是:生成RDB快照、传输RDB快照:
- 生成RDB快照:尽管我们使用的是bgsave后台生成RDB快照,但是如果数据量比较大,那么尽管是后台执行,也有可能影响到主线程的效率甚至阻塞,导致主线程挂掉
- 传输RDB快照:传输RDB文件会占用主服务器的网络带宽,会影响主服务器响应命令请求
因此我们可以限制一个master上的slave结点个数,如果实在太多slave,则可以采用主-从-从链式结构,减少master压力:
也就是说数据同步不一定需要主节点“亲力亲为”,可以选个“副总”帮助去做数据同步
🎯 主从数据不一致
主从结点之间的写命令发送是异步的,也就是说:
- 客户端发送写命令
- 主服务器执行写命令,并异步把写命令发送给从服务器
- 主服务器执行完写命令就会把结果返回给客户端
- 并不会等待从服务器执行完命令再返回结果给客户端
- 在从服务器还没有执行主服务器发送过来的命令时,去读取就会有主从结点的数据不一致。 也就是说无法实现主从数据时刻保持一致,也就是强一致性,除非进行同步,但是同步会很影响效率。
那我们如何去处理这种主从数据不一致的情况呢?
- 从硬件的角度:保证主从结点间通信状况,保证网络连接状况良好
- 从软件的角度:我们可以自己弄一个检测程序,来检测主从结点的数据差
- 通过Redis的INFO replication拿到主从结点的offset,得到他们的差值
- 对于差值大于我们指定的差值的时候,就让客户端不与这个结点通信
📦 主从切换的数据丢失
🎆 异步复制同步丢失
在前文中我们提到,Redis主从结点之间的数据复制,是异步复制的,也就是说可能存在:
- 主服务器存在很多写命令没有传给从服务器,数据同步需要比较久的时间
- 然后客户端发送写命令
- 主服务器执行完写命令
- 返回结果给客户端
- 但是主服务器返回给客户端结果后和同步数据前,发生了Redis宕机,那么数据就会丢失
那我们如何尽可能的去减少数据丢失呢?
Redis配置中有个参数min-slaves-max-lag,Redis会根据当前数据同步的速度,判断出同步完成需要的时间,如果时间超过了min-slaves-max-lag,便不再接受客户端的请求,这样即使丢数据,也只会丢这10s内的数据
那么总不能为了宕机这种特殊情况,一直不处理请求,此时我们可以把数据线丢进本地缓存或者磁盘或者消息队列,这样等待Redis恢复正常,再去读取这些消息即可。
🎇 集群脑裂
如果主节点的网络失联,与所有的从节点都失联了,但是客户端并不知情,数据还是在往这个结点传,这些数据都写在了主节点的缓冲区中,此时,哨兵发现了主节点失联了,于是选出了另外一个主节点,此时就出现了所谓的:集群脑裂——此时集群有两个主节点
然后原主节点的网络好了,但是由于哨兵机制,现在原主节点降级为从节点,会与现主节点做一次全量同步,这样在主节点网络失联期间的数据就丢失了
那我们如何去减少集群脑裂丢失的数据呢?
在Redis的配置文件中有两个参数帮助我们解决问题:
min-slaves-to-write:主节点至少有x个从节点,否则主结点禁止写数据min-slaves-max-lag:主从复制的延迟不能超过x秒,否则主节点禁止写数据
主节点连接的从节点中至少有 x 个从节点,并且主节点进行数据复制时的 ACK 消息延迟不能超过 x 秒,原主节点就会被限制接收客户端写请求,客户端也就不能在原主节点中写入新数据了
💬 总结
本文讲了Redis主从复制进行数据同步的原理、数据不一致或者丢失的情况以及解决办法
- 对于主从复制的同步数据,我们分别介绍了:
- 第一次同步时进行的全量复制
- 第一次同步结束后命令传播的方式
- 增量复制的使用情况以及使用流程
- 对于数据的丢失和不一致,我们分别介绍了:
- 主从数据由于异步复制导致无法保证强一致性
- 如何减少主从数据不一致带来的影响
- 异步复制同步丢失 和 集群脑裂 带来的数据丢失问题 相信读完今天的文章,大家能对Redis集群的底层原理有了更深一步的理解~
🍁 友链
✒写在最后
都看到这里啦~,给个点赞再走呗~,也欢迎各位大佬指正以及补充,在评论区一起交流,共同进步!也欢迎加微信一起交流:Goldfish7710。咱们明天见~