分布式系统中存在一个非常关键的问题 – 单点问题;单点问题指服务器程序只有一个节点,即只使用一个物理服务器来部署服务。这会导致一些问题:
为了解决单点问题,通常会把数据复制多个副本部署到其他服务器,以满足故障恢复和负载均衡等需求。其中,Redis 存在以下三种部署方式:主从模式、主从 + 哨兵模式 以及 集群模式。本节我们来学习 Redis 中主从模式的实现方式及其原理。
主从模式是指将 Redis 服务部署到多个物理服务器上,然后让其中的一个节点作为 “主节点”,让其他节点作为 “从节点”。其中主节点与从节点的关系如下:
通过主从复制的方式我们能够有效的解决单点问题:
要配置 Redis 主从结构,首先需要启动多个 Redis 服务,且这些 Redis 服务需要部署在不同服务器上,这样才能起到分布式的效果。但由于本人只有一台云服务器,因此在一台服务器上启动多个 redis-server 进程来模拟。
如下,服务器上启动了三个 redis-server:
[root@VM-8-13-centos redis]# pwd
/etc/redis
[root@VM-8-13-centos redis]# ll *.conf
-rw-r----- 1 redis root 61888 Aug 25 14:19 redis.conf
-rw-r----- 1 redis root 9837 Oct 25 2021 redis-sentinel.conf
-rw-r----- 1 root root 61888 Sep 1 14:42 redis_slave1.conf
-rw-r----- 1 root root 61888 Sep 1 14:43 redis_slave2.conf
[root@VM-8-13-centos redis]# redis-server redis.conf --port 6379
[root@VM-8-13-centos redis]# redis-server redis_slave1.conf --port 6380
[root@VM-8-13-centos redis]# redis-server redis_slave2.conf --port 6381
[root@VM-8-13-centos redis]# ps axj | grep redis
1 6022 6022 6022 ? -1 Ssl 0 0:00 redis-server 0.0.0.0:6379
1 7521 7521 7521 ? -1 Ssl 0 0:00 redis-server 0.0.0.0:6380
1 7585 7585 7585 ? -1 Ssl 0 0:00 redis-server 0.0.0.0:6381
3429 7637 7636 3240 pts/0 7636 S+ 0 0:00 grep --color=auto redis
目前这几个节点并没有构成主从结构,而是三个的节点,而配置主从结构的方法有三种:
slaveof {masterHost} {masterPort} ,此时配置随 Redis 启动生效。--slaveof {masterHost} {masterPort} 生效。slaveof {masterHost} {masterPort} 生效。其中,后面两种方法都是临时配置,即 redis-server 重启后需要重新执行 slaveof 命令,而第一种方式是永久生效的,不过修改配置文件后需要重启 redis-server 才能让配置生效。
下面我们演示第一种方法:
首先,修改配置文件 redis_slave1.conf 与 redis_slave2.conf,追加 salveof 配置。
# 主从复制
slaveof 127.0.0.1 6379
然后,重新启动端口号为 6380 与 6381 的节点,观察其与 6379 节点的关系:
如上,Redis 子节点与从节点之间会建立两个 TCP 连接,一个用于接收主节点命令的命令连接(主节点将所有的写命令发送到这个连接,从节点接收这些命令并执行,以此来保证数据的一致性),一个用于支持发布/订阅功能的订阅连接。
此时,我们在主节点上进行的任何数据修改操作,从节点都会同步,且从节点不允许修改:
127.0.0.1:6380> set k1 111
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>
同时,我们可以通过 info replication 命令查看主节点与从节点的复制信息:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=2954,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=2968,lag=0
master_replid:12acce366cb00f35ebb02447c42ce61af38b3528
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2968
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:2968
127.0.0.1:6379>
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:2982
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:12acce366cb00f35ebb02447c42ce61af38b3528
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2982
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:2982
127.0.0.1:6380>
对于已经建立好的主从复制关系,我们可以使用 slave no one 将其断开,此命令由从节点执行,执行后从节点将变成一个的节点,且无法再同步主节点修改的数据,但从节点中原有的数据并不会丢弃。
需要注意的是:在从节点中执行 slave no one 命令是临时性的,即从节点重启后仍然会复制主节点;想要永久生效需要删除或修改配置文件中的 slaveof 配置项。
另外,slave no one 命令配合 slaveof 命令使用可以实现切主效果,即将当前从节点的数据源切换到另一个主节点。切主操作的主要流程如下:
主从复制还有一些其他相关的特性,如下:
安全性:对于数据比较重要的节点,主节点会通过设置 requirepass 参数进行密码验证,这时所有的客户端访问必须使用 auth 命令实行校验。从节点与主节点的复制连接是通过一个特殊标识的客户端来完成,因此需要配置从节点的 masterauth 参数与主节点密码保持一致,这样从节点才可以正确地连接到主节点并发起复制流程。
只读:默认情况下,从节点使用 slave-read-only yes 配置为只读模式。由于复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,因此修改从节点会造成主从数据不一致,所以不建议修改从节点的只读模式。
传输延迟:主从节点一般部署在不同机器上,复制时的网络延迟就成为需要考虑的问题。Redis 为我们提供了 repl-disable-tcp-nodelay 参数用于控制是否关闭 TCP_NODELAY,默认为 no,即开启 tcp-nodelay 功能,说明如下 (TCP 内部的 nagle 算法):
为 no 即不关闭 TCP_NODELAY 时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如 同机房部署。
为 yes 即关闭 TCP_NODELAY 时,主节点会合并较小的 TCP 数据包从而节省带宽。默认发送时间间隔取决于 Linux 的内核,一般默认为 40 毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂的场景,如跨机房部署。
Redis 拓扑结构指 Redis 主从节点之间按照怎样的方式来组织连接。Redis 的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从、树状主从结构。
一主一从结构
一主一从结构是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持。
如下图所示,当应用写命令并发量较高且需要持久化时,可以只在从节点上开启 AOF,这样保证数据安全性的同时也避免了持久化对主节点的性能干扰。但这种设定有一个严重缺陷,即主节点宕机后不能让其自动重启,而是需要先从从节点获取 AOF 文件,然后使用该 AOF 文件进行重启与数据恢复,否则会导致数据丢失。
一主多从结构
一主多从结构使得应用端可以利用多个从节点实现读写分离,从而提高并发量。(实际开发中,一般读请求都要远多于写请求)
如下图所示,对于读比重较大的场景,可以把读命令负载均衡到不同的从节点上来分担压力,同时一些耗时的读命令可以指定一台专门的从节点执行,避免破坏整体的稳定性。但缺点在于,对于写并发量较高的场景,多个从节点 会导致主节点写命令的多次发送 从而加重主节点的负载。
树状主从结构
树形主从结构使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主机节点 (主机节点而不是主节点,其他从节点的主节点仍然是根节点) 继续向下层复制。通过引入复制中间层,可以有效降低主节点 “写” 负载和需要传送给从节点的数据量。
如下图所示,数据写入节点 A 之后会同步给 B 和 C 节点,B 节点进一步把数据同步给 D 和 E 节点。当主节点需要挂载多个从节点时为了避免对主节点的性能干扰,可以采用这种拓扑结构。但它的缺点在于,由于主节点数据需要逐层向下同步,因此同步的延时比较高。
Redis 复制建立以及运行的流程如下:
ping,主节点返回 pong。(在应用层面上验证主节点能够正常工作)requirepass 参数,需要进行密码验证。Redis 使用 psync 命令完成主从数据同步,同步分为全量复制和部分复制:
psync 命令的语法如下:
PSYNC replicationid offset
info replication 中的 master_repl_offset 指标中;从节点也会每秒钟上报自身的复制偏移量给主节点 (同步进度),并保存在 slave 行的 offset 字段中。?,offset 的值为 -1,此时表示进行全量复制。重连时,如果 replid 与 offset 为某个具体的值,则表示进行部分复制。我们可以通过 info replication 命令查看主从节点相关的复制信息:
主节点:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=114282,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=114282,lag=1
master_replid:12acce366cb00f35ebb02447c42ce61af38b3528
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:114282
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:114282
如上,主节点修改命令字节数 master_repl_offset 为 114282,从节点 slave0 和 slave1 的同步进度 offset 都为 114282,表示数据全部都已经同步了。
psync 命令的运行流程如下:
PSYNC 命令给主节点,且replid 和 offset 的默认值分别是 ? 和 -1。PSYNC 参数和自身数据情况决定响应结果:
+FULLRESYNC,则从节点需要进行全量复制流程。+CONTINUE,从节点进行部分复制流程。-ERR,说明 Redis 主节点版本过低,不支持 PSYNC 命令。从节点可以使用 SYNC 命令进行全量复制。(二者的区别在于,sync 会阻塞 redis-server,psync 则不会阻塞)全量复制流程如下:
PSYNC 命令给主节点进行数据同步,由于是第一次进行复制,从节点没有主节点的运行 ID 和复制偏移量,所以发送 PSYNC ? -1。+FULLRESYNC 响应。BGSAVE 进行 RDB 文件的持久化。BGREWRITE 操作,得到最近的 AOF 文件。有磁盘复制 VS 无磁盘复制:
Redis 从 2.8.18 版本开始支持无磁盘复制。即主节点在执行 RDB 生成流程时,不会生成 RDB 文件到磁盘中,而是直接把生成的 RDB 数据通过网络发送给从节点,然后从节点直接直接将收到的 RDB 数据进行加载,这样就节省了一系列的写硬盘和读硬盘的操作开销。
但即使是这样,全量复制的开销也很大,因为要将所有的数据通过网络进行传输,而网络传输的代价比写磁盘要高很多。
部分复制流程如下:
repl-timeout 时间,主节点会认为从节点故障并终止复制连接。repl-backlog-buffer 中。replicationId 和复制偏移量作为 PSYNC 的参数发送给主节点,请求进行部分复制。PSYNC 请求后,进行必要的验证。然后根据 offset 去 repl-backlog-buffer 查找合适的数据,并响应 +CONTINUE 给从节点。特别注意:
- 关于复制积压缓冲区:复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为 1MB,当主节点有连接的从节点时被创建,这时主节点响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。因此它能实现保存最近已复制数据的功能,可以用于部分复制和复制命令丢失的数据补救。
但由于缓冲区大小是固定的,因此如果从节点重连时需要的数据,已经超出了主节点的积压缓冲区的范围,则无法进行部分复制,只能全量复制了。
- 关于 replid 与 runid:如果大家在网上搜索 “Redis 主从复制” 相关的文章,会发现很多文章都会提到服务器运行ID (runid),文中提到由于服务器每次运行的 runid 都不同,因此从节点可以根据自己保存的 runid 与主节点现有的 runid 是否相同来判断重连的是否是之前的主节点,从而只进行部分复制。
但其实 runid 与主从复制并没有关系,主从复制时使用的是 replid。如果主节点与从节点之间发生网络抖动而断连,此时从节点master_replid2中会保存主节的 replid,然后自己成为主节点。当网络恢复二者重连时,从节点向主节点发送psync master_replid2 offset,主节点根据 replid 是否相同以及 offset 与复制积压缓冲区的情况来决定是否进行部分复制。
因此,在主从复制中起作用的是 replid,runid 作用于哨兵。
主从节点在建立复制连接后,会进行实时复制 (实时数据同步)。主节点会把自己收到的修改命令,通过 TCP 长连接的方式,源源不断地传输给从节点 (注意发送的是命令而不是二进制数据)。从节点会根据这些请求来同时修改自身的数据,以保持和主节点数据的一致性。
另外,这样的长连接,需要通过心跳包的方式来维护连接状态:(这里的心跳是指应用层自己实现的心跳,而不是 TCP 自带的心跳)
replconf ack {offset} 命令,给主节点上报自身当前的复制偏移量。如果主节点发现从节点通信延迟超过 repl-timeout 配置的值(默认 60 秒),则判定从节点下线,断开复制客户端连接。从节点恢复连接后,心跳机制继续进行。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务