前言
在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他服务器,满⾜故障恢复和负载均衡等需求。Redis 也是如此,它为我们提供了复制的功能,实现了相同数据的多个 Redis 副本。复制功能是⾼可⽤ Redis 的基础,哨兵和集群都是在复制的基础上构建的。
单点问题
如果某个服务程序只部署在一个节点上(一个物理服务器上),那么当这个物理服务器挂掉后就无法提供该服务了,这就影响了该服务的可用性,并且一个物理服务器的资源是有限的,如果访问量过高也会导致无法提供服务。
引入分布式主要也是为了解决上述的单点问题,在分布式系统中,往往希望有多个服务器来部署 Redis 服务,形成一个 Redis 服务集群。当某个服务器出现问题后,其余的 Redis 服务器可以顶替,解决了可用性的问题。并且多个服务器能够分摊流量,通过 Redis 集群可以承担更大的访问量。
主从模式
主从模式是 Redis 服务器的部署方式。
在若干个 Redis 节点中,有的是主节点,有的是从节点,从节点得听主节点的,从节点上的数据得跟随主节点变化,从节点上的数据和主节点要保持一致。从节点就是主节点的副本。
流程:一开始主节点中有一批数据,引入从节点后就需要将主节点中的所有数据同步给从节点,后续主节点上对数据做的任何修改都会同步给从节点。
目的
准确的说,主从模式是对读操作的并发量与可用性的提高。因为对数据的修改操作只能由主节点完成,再由主节点同步给从节点,而主节点和从节点都可以完成读操作,因为它们保存的数据都是相同的。
在实际业务场景中,读操作往往是远大于写操作的。所以提高了读操作的并发量和可用性是非常有必要的。
从节点的数据是否可以修改
从节点上的数据是不能修改的,数据完全同步于主节点,要确保从节点的数据和主节点完全相同,避免歧义。
配置主从模式
现在我们在一个物理服务器上配置多个 Redis 服务器来实现主从模式。
参与复制的 Redis 实例划分为主节点(master)和从节点(slave)。每个从结点只能有⼀个主节点, ⽽⼀个主节点可以同时具有多个从结点。复制的数据流是单向的,只能由主节点到从节点。
由于我们需要在一个物理服务器上创建 2 个子 Redis 服务器,一个主 Redis 服务器,所以先创建一个目录,然后将 Redis 配置文件复制到该目录中。
创建子 Redis 服务器配置文件
创建 redis-conf 目录
mkdir redis-conf
将配置文件复制到 redis-conf 目录
cp /etc/redis.conf redis-conf/redis01.conf
cp /etc/redis.conf redis-conf/redis02.conf
配置 Redis 服务器后台运行
在 Redis 配置文件中 daemonize yes 配置项表示让 Redis 服务器按照后台进程的方式运行,要将两个配置文件中的 daemonize 配置项进行修改
配置 Redis 节点
一个 Redis 主节点和 两个 Redis 子节点的端口号在同一个服务器上不能相同,所以我们要重新设置一下子 Redis 服务器的端口号
将 Redis 子节点的端口号配置为 6380 和 6381
而且 3 个 Reids 服务器的 RDB 和 AOF 文件路径和文件名不能相同,要不然 3 个 Redis 服务器就共用一个 RDB 和 AOF 文件了,要修改两个子 Redis 服务器 RDB 和 AOF 文件的路径和文件名。
启动 Redis 服务器
按照不同的配置文件启动 Redis 服务器
redis-server ./redis-conf/redis01.conf
redis-server ./redis-conf/redis02.conf
查询当前进程状态,可以看到 6379,6380,6381 端口号的 Redis 服务器均启动
ps -aux|grep redis
连接 Redis 服务器
连接主 Redis 服务器
redis-cli
连接子 Redis 服务器,6380 是端口号
redis-cli -p 6380
到此,我们已经在一个物理服务器上准备好了多个 Redis 服务器,但现在这些 Redis 服务器之间没有任何关系,我们需要配置它们之间的主从关系。
配置从节点
配置从节点的⽅式有以下三种:
-
在配置⽂件中加⼊slaveof {masterHost} {masterPort}随 Redis 启动⽣效。
-
在 redis-server 启动命令时加⼊ --slaveof {masterHost} {masterPort}⽣效。
-
直接使⽤ redis 命令:slaveof {masterHost} {masterPort}⽣效。
我们主要采用的是在配置文件中配置从节点,因为这种方式一劳永逸,之后重启 Redis 服务器也不需要重新配置。
slaveof 127.0.0.1 6379
在两个子 Redis 服务器的配置文件中加上如上配置,就将这两个子 Redis 服务器与主 Redis 服务器绑定起来。
记得修改配置文件后要重启 Redis 服务器才会生效。
重启后,主 Redis 服务器和子 Redis 服务器便建立了连接,子 Redis 服务器已经不允许修改数据。
查看复制相关状态
此时主 Redis 服务器和子 Redis 服务器已经建立了连接,主 Redis 服务器上的数据需要复制给子 Redis 服务器,通过info replication 命令查看复制相关状态
info replication
主 Redis 服务器复制状态
- role:表示该 Redis 服务器的身份,master 表示主 Redis 服务器
- connected_slaves:表示关联的子 Redis 服务器个数
- slaveX:ip :表示子 Redis 服务器的 ip 地址,port 是端口号,state 表示状态,online 是在线。offset 表示该子 Redis 服务器已同步数据到哪个位置,
子 Redis 服务器复制状态
断开复制
slaveof 命令不但可以建⽴复制,还可以在从节点执⾏ slaveof no one 来断开与主节点复制关系。
slaveof no one
断开复制主要流程:
1)断开与主节点复制关系。
2)从节点晋升为主节点。从节点断开复制后并不会抛弃原有数据,只是⽆法再获取主节点上的数据变化
切主操作
通过 slaveof 命令还可以实现切主操作,将当前从节点的数据源切换到另⼀个主节点。执⾏ slaveof {newMasterIp} {newMasterPort} 命令即可。
slaveof {newMasterIp} {newMasterPort}
切主操作主要流程:
1)断开与旧主节点复制关系。
2)与新主节点建⽴复制关系。
3)删除从节点当前所有数据。
4)从新主节点进⾏复制操作。
注意:这些通过命令进行的配置,在 Redis 重启以后就会消失,以 Redis 配置文件中的配置重新启动 Redis 服务器。
安全性
对于数据⽐较重要的节点,主节点会通过设置 requirepass 参数进⾏密码验证,这时所有的客户端访问必须使⽤ auth命令实⾏校验。从节点与主节点的复制连接是通过⼀个特殊标识的客户端来完成,因此需要配置从节点的 masterauth参数与主节点密码保持⼀致,这样从节点才可以正确地连接到主节点并发起复制流程。
只读
默认情况下,从节点使⽤ slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节点,对于从节点的任何修改主节点都⽆法感知,修改从节点会造成主从数据不⼀致。所以建议线上不要修改从节点的只读模式
传输延迟
主从节点⼀般部署在不同机器上,复制时的⽹络延迟就成为需要考虑的问题,Redis 为我们提供 了repl-disable-tcp-nodelay 参数⽤于控制是否关闭 TCP_NODELAY,默认为 no,即开启 tcpnodelay功能,说明如下:
• 当关闭时,主节点产⽣的命令数据⽆论⼤⼩都会及时地发送给从节点,这样主从之间延迟会变⼩, 但增加了⽹络带宽的消耗。适⽤于主从之间的⽹络环境良好的场景,如同机房部署。
• 当开启时,主节点会合并较⼩的 TCP 数据包从⽽节省带宽。默认发送时间间隔取决于 Linux 的内 核,⼀般默认为 40 毫秒。这种配置节省了带宽但增⼤主从之间的延迟。适⽤于主从⽹络环境复杂 的场景,如跨机房部署。
拓扑结构
Redis 的复制拓扑结构可以⽀持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:⼀主⼀ 从、⼀主多从、树状主从结构。
一主一从
⼀主⼀从结构是最简单的复制拓扑结构,⽤于主节点出现宕机时从节点提供故障转移⽀持,如图所⽰。
当应⽤写命令并发量较⾼(主节点压力较大)且需要持久化时,可以只在从节点上开启 AOF(关闭主节点的 AOF ),这样既可以保证数据安全性同时也避免了持久化对主节点的性能⼲扰。但需要注意的是,当主节点关闭持久化功能时,如果主节点宕机要避免⾃动重启操作。
当主 Redis 服务器宕机后,如果自动重启,没有相应的 AOF 文件进行数据恢复,那么主 Redis 服务器启动后就没有数据,通过数据同步,也会将子 Redis 节点中的数据清空。
⼀主多从
⼀主多从结构(星形结构)使得应⽤端可以利⽤多个从节点实现读写分离,如下图所⽰。对于读⽐重较⼤的场景,可以把读命令负载均衡到不同的从节点上来分担压⼒。同时⼀些耗时的读命令可 以指定⼀台专⻔的从节点执⾏,避免破坏整体的稳定性。
但这种拓扑结构有个很大的缺点,由于多个子 Redis 节点都依赖于主 Redis 节点来进行数据同步,这会导致主 Redis 节点承担了很大的压力(消耗了很多网络带宽),而且写操作还只能交给主 Redis 节点处理,主 Redis 节点真的压力山大,通过下面的树形主从便能解决
树形主从
树形主从结构(分层结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的数据源继续向下层复制。通过引⼊复制中间层,可以有效减轻主节点的压力,如下图所⽰:
此时主节点只负责向少数的子节点同步数据,而子节点会继续向下同步数据。虽然这个拓扑结构解决了主节点压力大的问题,但是也引来了同步延迟高的问题。因为此时数据需要一层一层的进行传递,不是由主节点直接发送,所以会导致同步完所有子节点的耗时加长。子节点间会出现短暂的数据不同步情况。
复制过程
主从节点建⽴复制流程图:
输入命令让子节点和主节点建立复制关系
- 开始配置主从同步关系之后,从节点只保存主节点的地址信息,此时建⽴复制流程还没有开始, 在从节点 6380 执⾏ info replication 可以看到如下信息:
从统计信息可以看出,主节点的 ip 和 port 被保存下来,但是主节点的连接状态 (master_link_status)是下线状态。
2.从节点会尝试和主节点建立 TCP 连接,其中涉及到 TCP 的 3 次握手
3.发送 ping 命令。连接建⽴成功之后,从节点通过 ping 命令确认主节点在应⽤层上是⼯作良好的。 如果 ping 命令的结果 pong 回复超时,从节点会断开 TCP 连接,等待下次重新建⽴连接。
4.权限验证。如果主节点设置了 requirepass参数,则需要密码验证,从节点通过配置 masterauth参数来设置密码。如果验证失败,则从节点的复制将会停⽌。
5.同步数据集。对于⾸次建⽴复制的场景(从节点中没有主节点的任何数据),主节点会把当前持有的所有数据全部发送给从节点,这步操作基本是耗时最⻓的,所以⼜划分称两种情况:全量同步和部分同步
6.命令持续复制。当从节点复制了主节点的所有数据之后,针对之后的修改命令,主节点会持续的把 命令发送给从节点,从节点执⾏修改命令,保证主从数据的⼀致性。
数据同步 psync
Redis 使⽤ psync 命令完成主从数据同步,子节点向父节点发送 psync 命令进行数据同步,同步过程分为:全量复制和部分复制。
• 全量复制:⼀般⽤于初次复制场景,Redis 早期⽀持的复制功能只有全量复制,它会把主节点全部数据⼀次性发送给从节点,当数据量较⼤时,会对主从节点和⽹络造成很⼤的开销。
• 部分复制:⽤于处理在主从复制中因**⽹络闪断**等原因造成的数据丢失场景,当从节点再次连上主节 点后,如果条件允许,主节点会补发数据给从节点。因为补发的数据远⼩于全量数据,可以有效避免全量复制的过⾼开销
PSYNC 的语法格式
PSYNC replicationid offset
如果 replicationid 设为 ? 并且 offset 设为 -1 此时就是在尝试进⾏全量复制.
如果 replicationid offset 设为了具体的数值,则是尝试进⾏部分复制.
当 replicationid 和父节点的 replicationid 相同,就说明当前子节点原来和父节点就是主从关系,只不过因为某些原因断开了连接(如网络波动)
而 offset 就描述了数据复制的进度,如果因为某些原因导致主从复制的连接中断(网络波动),此时父节点会将发送给子节点同步数据的命令放到一个积压缓冲区中。但这个积压缓冲区的容量有限,只能记录最新的一些数据修改命令,当从节点重新和主节点建立连接后,主节点会根据子节点的 offset 判断子节点未同步的数据是否在积压缓冲区内,如果存在就进行部分复制即可,若断开连接太久,积压缓冲区中的命令不足以同步数据,那么只能进行全量复制。
replicationid/replid(复制id)
主节点的复制 id,.主节点重新启动,或者从节点晋级成主节点,都会⽣成⼀个 replicationid. (同⼀个节点,每次重启,⽣成的 replicationid 也会变化)
从节点在和主节点建⽴连接之后, 就会获取到主节点的 replicationid.
通过 info replication 即可看到 replicationid
info replication
关于 master_replid 和 master_replid2
每个节点需要记录两组 master_replid .这个设定解决的问题场景是这样的:
⽐如当前有两个节点 A 和 B, A 为 master, B 为 slave.此时 B 就会记录 A的 master_replid. 如果⽹络出现抖动, 通过心跳检验,B 以为 A 挂了, B ⾃⼰就会成为主节点. 于是 B 就会有自己的master_replid. 此时就会使⽤ master_replid2 来保存之前 A 的 master_replid.
• 后续如果⽹络恢复了, B 就可以根据 master_replid2 找回之前的主节点.
• 后续如果⽹络没有恢复, B 就按照新的 master_replid ⾃成⼀派,继续处理后续的数据.
offset (偏移量)
参与复制的主从节点都会维护⾃⾝复制偏移量。主节点(master)在处理完写⼊命令后,会把命令的字节⻓度做累加记录,统计信息在 info replication 中的 master_repl_offset 指标中,
从节点(slave)每秒钟上报⾃⾝的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量, 统计指标如下
从节点在接受到主节点发送的命令后,也会累加记录⾃⾝的偏移量。统计信息在 info replication 中的 slave_repl_offset 指标中:
当主从节点的偏移量相同时,就代表主从节点的数据一致。
replid + offset 共同标识了⼀个 "数据集".如果两个节点, 他们的 replid 和 offset 都相同,则这两个节点上持有的数据, 就⼀定相同.
psync 运⾏流程
1)从节点发送 psync 命令给主节点,replid 和 offset 的默认值分别是 ? 和 -1.(全量复制)
2)主节点根据 psync 参数和⾃⾝数据情况决定响应结果:
• 如果回复 +FULLRESYNC replid offset,则从节点需要进⾏全量复制流程。
• 如果回复 +CONTINEU,从节点进⾏部分复制流程。
• 如果回复 -ERR,说明 Redis 主节点版本过低,不⽀持 psync 命令。从节点可以使⽤ sync 命令进⾏全量复制。
• psync ⼀般不需要⼿动执⾏. Redis 会在主从复制模式下**⾃动调⽤执⾏.**
• sync 会阻塞 redis server 处理其他请求.psync 则不会.
全量复制
全量复制是 Redis 最早⽀持的复制⽅式,也是主从第⼀次建⽴复制时必须经历的阶段。全量复制的运⾏流程如图所⽰:
全量复制流程
1)从节点发送 psync 命令给主节点进⾏数据同步,由于是第⼀次进⾏复制,从节点没有主节点的复制 ID 和复制偏移量,所以发送 psync ? -1。
2)主节点根据命令,解析出要进⾏全量复制,回复 +FULLRESYNC 响应。
3)从节点接收主节点的运⾏信息进⾏保存。
4)主节点执⾏ bgsave 进⾏ RDB ⽂件的持久化。
5)主节点发送 RDB ⽂件给从节点,从节点保存 RDB 数据到本地硬盘。
6)主节点将从⽣成 RDB 到接收完成期间执⾏的写命令,写⼊缓冲区中,等从节点保存完 RDB ⽂件后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写⼊到收到的 rdb ⽂件中.保持主从⼀致性。
7)从节点清空⾃⾝原有旧数据。
8)从节点加载 RDB ⽂件得到与主节点⼀致的数据
9)如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进⾏ bgrewrite 操作,得到最近的 AOF ⽂件。
通过分析全量复制的所有流程,我们会发现全量复制是⼀件**⾼成本**的操作:主节点 bgsave 的时间, RDB 在⽹络传输的时间,从节点清空旧数据的时间,从节点加载 RDB 的时间等。所以⼀般应该尽可能避免对已经有⼤量数据集的 Redis 进⾏全量复制。
有磁盘复制 vs ⽆磁盘复制(diskless)
默认情况下, 进⾏全量复制需要主节点⽣成 RDB ⽂件到主节点的磁盘中, 再把磁盘上的 RDB ⽂件通过发送给从节点.
Redis 从 2.8.18 版本开始⽀持⽆磁盘复制.主节点在执⾏ RDB ⽣成流程时, 不会⽣成 RDB ⽂ 件到磁盘中了, ⽽是直接把⽣成的 RDB 数据通过⽹络发送给从节点.这样就节省了⼀系列的写硬盘和读硬盘的操作开销.
部分复制
部分复制主要是 Redis针对全量复制的过⾼开销做出的⼀种优化措施,使⽤ psync replicationId offset 命令实现。当从节点正在复制主节点时,如果出现**⽹络闪断或者命令丢失**等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区存在数据则直接发送给从节点, 这样就可以保持主从节点复制的⼀致性。
(但也要看积压缓冲区中的数据是否能让从节点同步,如果从节点断开连接太长时间,可能缓冲区中的数据不足以让从节点同步,就只能进行全量复制)补发的这部分数据⼀般远远⼩于全量数据,所以开销很⼩。 整体流程如图所⽰:
部分复制流程
1)当主从节点之间出现⽹络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并中断复制连接。
2)主从连接中断期间主节点依然响应命令,但这些复制命令都因⽹络中断⽆法及时发送给从节点,所 以暂时将这些命令滞留在复制积压缓冲区中。
3)当主从节点⽹络恢复后,从节点再次连上主节点。
4)从节点将之前保存的 replicationId 和 复制偏移量作为 psync 的参数发送给主节点,请求进⾏部分复制。
5)主节点接到 psync 请求后,进⾏必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据, 并响应 +CONTINUE 给从节点。
6)主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性。
复制积压缓冲区
复制积压缓冲区是保存在主节点上的⼀个固定⻓度的队列,默认⼤⼩为 1MB,当主节点有连接的从节 点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写⼊复制积压缓冲区(复制积压缓冲区始终保存最新的一批写命令),如图所⽰:
由于缓冲区本质上是先进先出的定⻓队列,所以能实现保存最近复制命令功能,⽤于部分复制和复制命令丢失时的数据补救。复制缓冲区相关统计信息可以通过主节点的 info replication 中:
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最⼤⻓度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可⽤范围
repl_backlog_histlen:1048576 // 已保存数据的有效⻓度
实时复制
主从节点在建⽴复制连接后,主节点会把⾃⼰收到的修改操作,通过 tcp ⻓连接的⽅式,源源不断的传输给从节点.从节点就会根据这些请求来同时修改⾃⾝的数据.从⽽保持和主节点数据的⼀致性.
另外,这样的⻓连接,需要通过⼼跳包的⽅式来维护连接状态.(这⾥的⼼跳是指应⽤层⾃⼰实现的⼼跳, ⽽不是 TCP ⾃带的⼼跳).
1)主从节点彼此都有⼼跳检测机制,各⾃模拟成对⽅的客户端进⾏通信。
2)主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态。
3)从节点默认每隔 1 秒向主节点发送 replconf ack {offset} 命令,给主节点上报⾃⾝当前的复制偏移量。
如果主节点发现从节点通信延迟超过 repl-timeout 配置的值(默认 60 秒),则判定从节点下线,断开复制连接。从节点恢复连接后,⼼跳机制继续进⾏。
主从复制的缺点
-
从机多了,复制数据的延时⾮常明显.
-
主机挂了,从机不会升级成主机.只能通过**⼈⼯⼲预的⽅式恢复,但人工很难第一时间发现主机挂了这件事,所以哨兵机制**就是用来解决该问题的