Redis 高可用篇:你管这叫主从架构数据同步原理?
开篇寄语
“ 问题 = 机会。遇到问题的时候,内心其实是开心的,越大的问题意味着越大的机会。 任何事情都是有代价的,有得必有失,有失必有得,所以不必计较很多东西,我们只要想清楚自己要做什么,并且想清楚自己愿意为之付出什么代价,然后就放手去做吧! ”
1. 主从复制概述
“ 65 哥:有了 RDB 和 AOF 再也不怕宕机丢失数据了,但是 Redis 实例宕机了怎么实现高可用呢? ”
“ 65 哥:主从之间的数据如何保证一致性呢? ”
读操作:主、从库都可以执行; 写操作:主库先执行,之后将写操作同步到从库;
“ 65 哥:为何要采用读写分离的方式? ”
“ 65 哥:主从复制还有其他作用么? ”
故障恢复:当主节点宕机,其他节点依然可以提供服务; 负载均衡:Master 节点提供写服务,Slave 节点提供读服务,分担压力; 高可用基石:是哨兵和 cluster 实施的基础,是高可用的基石。
2. 搭建主从复制
“ 65 哥:怎么搭建主从复制架构呀? ”
配置文件 在从服务器的配置文件中加入 replicaof <masterip> <masterport>
启动命令 redis-server 启动命令后面加入 --replicaof <masterip> <masterport>
客户端命令 启动多个 Redis 实例后,直接通过客户端执行命令: replicaof <masterip> <masterport>
,则该 Redis 实例成为从节点。
replicaof 172.16.88.1 6379
3. 主从复制原理
“ 65 哥:主从库同步是如何完成的呢?主库数据是一次性传给从库,还是分批同步?正常运行中又怎么同步呢?要是主从库间的网络断连了,重新连接后数据还能保持一致吗? ”
第一次主从库全量复制; 主从正常运行期间的同步; 主从库间网络断开重连同步。
主从库第一次全量复制
“ 65 哥:我好晕啊,先从主从库间第一次同步说起吧。 ”
建立连接
“ 65 哥:从库怎么知道主库信息并建立连接的呢? ”
replicaof
并发送 psync
命令,表示要执行数据同步,主库收到命令后根据参数启动复制。命令包含了主库的 runID 和 复制进度 offset 两个参数。runID:每个 Redis 实例启动都会自动生成一个 唯一标识 ID,第一次主从复制,还不知道主库 runID,参数设置为 「?」。 offset:第一次复制设置为 -1,表示第一次复制,记录复制进度偏移量。
主库同步数据给从库
bgsave
命令生成 RDB 文件,并将文件发送给从库,同时主库为每一个 slave 开辟一块 replication buffer 缓冲区记录从生成 RDB 文件开始收到的所有写命令。发送新写命令到从库
“ 65 哥:主库将数据同步到从库过程中,可以正常接受请求么? ”
“ 65 哥:为啥从库收到 RDB 文件后要清空当前数据库? ”
replcaof
命令开始和主库同步前可能保存了其他数据,防止主从数据之间的影响。“ replication buffer 到底是什么玩意? ”
config set client-output-buffer-limit "slave 536870912 536870912 0"
“ 65 哥:主从库复制为何不使用 AOF 呢?相比 RDB 来说,丢失的数据更少。 ”
RDB 文件是二进制文件,网络传输 RDB 和写入磁盘的 IO 效率都要比 AOF 高。 从库进行数据恢复的时候,RDB 的恢复效率也要高于 AOF。
增量复制
“ 65 哥:主从库间的网络断了咋办?断开后要重新全量复制么? ”
repl_backlog_buffer
缓冲区,不管在什么时候 master 都会将写指令操作记录在 repl_backlog_buffer
中,因为内存有限, repl_backlog_buffer
是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。master_repl_offset
记录自己写到的位置偏移量,slave 则使用 slave_repl_offset
记录已经读取到的偏移量。repl_backlog_buffer
的已复制的偏移量 slave_repl_offset 也在不断增加。master_repl_offset
会大于 slave_repl_offset
。runID
,slave_repl_offset
发送给 master。master_repl_offset
与 slave_repl_offset
之间的命令同步给从库即可。“ 65 哥:repl_backlog_buffer 太小的话从库还没读取到就被 Master 的新写操作覆盖了咋办? ”
repl_backlog_buffer = second * write_size_per_second
second:从服务器断开重连主服务器所需的平均时间; write_size_per_second:master 平均每秒产生的命令数据量大小(写命令和数据大小总和);
2 * second * write_size_per_second
,这样可以保证绝大部分断线情况都能用部分重同步来处理。基于长连接的命令传播
“ 65 哥:完成全量同步后,正常运行过程如何同步呢? ”
主->从:PING
从->主:REPLCONF ACK
REPLCONF ACK <replication_offset>
检测主从服务器的网络连接状态。 辅助实现 min-slaves 选项。 检测命令丢失, 从节点发送了自身的 slave_replication_offset,主节点会用自己的 master_replication_offset 对比,如果从节点数据缺失,主节点会从 repl_backlog_buffer
缓冲区中找到并推送缺失的数据。注意,offset 和 repl_backlog_buffer 缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。
如何确定执行全量同步还是部分同步?
psync
的执行:从节点根据当前状态,发送 psync
命令给 master:如果从节点从未执行过 replicaof
,则从节点发送psync ? -1
,向主节点发送全量复制请求;如果从节点之前执行过 replicaof
则发送psync <runID> <offset>
, runID 是上次复制保存的主节点 runID,offset 是上次复制截至时从节点保存的复制偏移量。主节点根据接受到的 psync
命令和当前服务器状态,决定执行全量复制还是部分复制:runID 与从节点发送的 runID 相同,且从节点发送的 slave_repl_offset
之后的数据在repl_backlog_buffer
缓冲区中都存在,则回复CONTINUE
,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;runID 与从节点发送的 runID 不同,或者从节点发送的 slave_repl_offset 之后的数据已不在主节点的 repl_backlog_buffer
缓冲区中 (在队列中被挤出了),则回复从节点FULLRESYNC <runid> <offset>
,表示要进行全量复制,其中 runID 表示主节点当前的 runID,offset 表示主节点当前的 offset,从节点保存这两个值,以备使用。
repl_backlog_buffer
的 slave_repl_offset 位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制。slave_repl_offset
,每个从库的复制进度也不一定相同。slave_repl_offset
发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。replication buffer 对应于每个 slave,通过 config set client-output-buffer-limit slave
设置。repl_backlog_buffer
是一个环形缓冲区,整个 master 进程中只会存在一个,所有的 slave 公用。repl_backlog 的大小通过 repl-backlog-size 参数设置,默认大小是 1M,其大小可以根据每秒产生的命令、(master 执行 rdb bgsave) +( master 发送 rdb 到 slave) + (slave load rdb 文件)时间之和来估算积压缓冲区的大小,repl-backlog-size 值不小于这两者的乘积。
replication buffer
是主从库在进行全量复制时,主库上用于和从库连接的客户端的 buffer,而 repl_backlog_buffer
是为了支持从库增量复制,主库上用于持续保存写操作的一块专用 buffer。repl_backlog_buffer
是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的。主库和从库会各自记录自己的复制进度,所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset
)发给主库,主库就可以和它独立同步。4. 主从应用问题
4.1 读写分离的问题
“ 65 哥:主从复制的场景下,从节点会删除过期数据么? ”
惰性删除:当客户端查询对应的数据时,Redis 判断该数据是否过期,过期则删除。 定期删除:Redis 通过定时任务删除过期数据。
“ 65 哥:那客户端通过从节点读取数据会不会读取到过期数据? ”
4.2 单机内存大小限制
总结
主从复制的作用:AOF 和 RDB 二进制文件保证了宕机快速恢复数据,尽可能的防止丢失数据。但是宕机后依然无法提供服务,所以便演化出主从架构、读写分离。 主从复制原理:连接建立阶段、数据同步阶段、命令传播阶段;数据同步阶段又分为 全量复制和部分复制;命令传播阶段主从节点之间有 PING 和 REPLCONF ACK 命令互相进行心跳检测。 主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注。
“ 65 哥:码哥你的图画的真好看,内容好,跟着你的文章我收获了很多,我要收藏、点赞、在看和分享。让更多的优秀开发者看到共同进步! ”
参考资料:
[1] redis 设计与实现(黄健宏)
[2] redis replication (http://redis.io/topics/replication)
[3] designing redis replication partial resync (http://antirez.com/news/31)
(4) Redis 核心技术与实战(https://time.geekbang.org/column/intro/329)
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️
评论