文章目录
- 前言: 引入分布式
- redis 在分布式系统中的主要部署方式
- 主从模式概述
- 主从复制能解决的问题
- 解决可用性问题
- 解决性能瓶颈问题
- 主从复制的配置
- 查看主从结构信息
- 断开主从结构以及修改主从结构信息
- 主从复制的基本流程
- 数据同步
- replid
- offset
- psync 的运行流程
- 全量复制的流程
- 部分复制的流程
- 实时复制的流程
- 小结
前言: 引入分布式
分布式系统, 涉及到一个很重要的问题: 单点问题. 如果某个服务器程序只有一个节点(只有一个服务器来部署应用程序), 可能会遇到以下问题:
- 可用性问题: 如果这台机器挂了, 服务就中断了.
- 性能瓶颈: 单台机器的性能以及并发量是比较有限的.
所以, 引入分布式系统主要就是为了解决上面的问题
以使用 redis 的视角来看, 往往希望有多个程序来部署 redis 服务, 从而构建出一个 redis 集群, 此时就可以让这个集群给整个分布式系统中的其它服务提供更稳定, 更高效的服务.
redis 在分布式系统中的主要部署方式
在分布式系统中, 希望使用多个服务器来部署 redis, 存在以下几个部署方式:
- 主从模式
- 主从 + 哨兵模式
- 集群模式
以上三个是 redis 在集群中的主要部署模式, 本章主要介绍的是 主从模式.
主从模式概述
在若干个 redis 节点中, 有的是 “主” 节点, 有的是 “从” 节点, 从节点上的数据要和主节点的数据保持一致.
引入从节点后, 本来在主节点上保存的数据要复制出来一份放入从节点中, 后续, 主节点对于数据有任何修改, 都会把修改的数据同步到从节点上. 总的来说, 从节点就是主节点的副本.
redis 主从模式中, 从节点上的数据是不允许修改的, 从节点只能读取数据而不能修改数据!
主从复制能解决的问题
主从复制是 redis 在分布式系统中的一种模式, 而分布式系统就是要解决上述提到的问题: 单点问题.
解决可用性问题
上述提到了, 单点结构最主要的缺点就是当这台唯一提供服务的机器挂了, 就无法在第一时间再次提供服务, 肯定会造成一定的损失, 而主从复制很好的解决了这个问题.
主从复制模式在原有的基础上添加了若干个 主节点/从节点, 当某一台机器挂了, 其它的机器能无缝衔接的提供服务, 这样就能保证 redis 的可用性.
如果是挂了某个从节点, 不会有啥影响, 此时继续从主节点或者其他存活的从节点中读取数据, 得到的效果是完全相同的.
如果挂掉了主节点, 那就会有一定的影响, 因为上述提到了从节点只能读数据而不能写数据, 只有主节点能够写数据, 所以当主节点挂了, 就不能写数据了. 虽然可用性是提高了, 但是还没有达到理想的程度.
那么能不能存在多个主节点呢? 这样当一个主节点挂了其它的主节点还能提供写服务. 答案是不能, 至少在主从复制这个模式下不能存在多个主节点, 一旦存在了多个主节点, 那么相互之间的数据同步将是一个问题. 所以上述主节点挂了的问题在主从复制的模式下是不能够解决的. 在下一篇文章 “哨兵模式” 中, 这个问题将会得到解决.
还有一种极端的情况, 当主从复制的机器都集中在同一个机房中, 如果这个机房出现问题, 整个机房的机器都挂了, 这样服务也会中断.
所以最稳妥的解决方案就是把这些机器分布在不同的机房中, 这样多个机器同时出问题的概率也会减小(异地多活).
解决性能瓶颈问题
单点结构还会遇到一个问题, 那就是性能瓶颈, 单个主机的性能和可支持的并发量是在是有限, 无法维持大型项目的服务, 而主从复制模式能够解决这个问题.
主从复制模式中, 从节点的数据和主节点中时刻保持一致, 因此当用户访问 redis 服务的时候, 从主节点和从节点中读到的数据是没有区别的.
当有客户端来读取数据的时候, 就可以从上述的节点中随机挑选出一个节点给客户端提供读取数据的服务.
就此, 引入了多个主机就相当于引入了更多的计算资源, 这样能支撑的并发量就能大幅度提高了. 从而解决单点模式的性能缺乏的问题.
主从复制的配置
这里使用同一台主机来配置主从复制的模式. 其中 6379 为主节点, 6380 和 6381 为从节点.
修改主从复制结构的方式有三种:
-
在配置文件中加入 slaveof {masterHost} {masterPort} 随 redis 启动生效.
-
在 redis-server 启动命令时加入 --slaveof {masterHost} {masterPort} 生效.
-
直接使⽤ redis 命令: slaveof {masterHost} {masterPort} 生效.
这里通过修改配置文件的方式来配置从节点的端口.
- 复制两份配置文件到别的文件夹中, 并且分别指定两个从节点的工作目录
root@server:~# mkdir redis-conf
root@server:~# cd redis-conf
root@server:~# mkdir slave1 slave2
root@server:~# cp /etc/redis/redis.conf ./slave1.conf
root@server:~# cp /etc/redis/redis.conf ./slave2.conf
- 修改两个配置文件, 分别将复制的两个配置文件中的 port 修改为 6380 和 6381. 代表两个从节点的端口号. 接着修改两个从节点工作目录的路径, 最后分别在两个配置文件的底端添加上从节点的配置项.
# 从节点 1
port 6380
# 改变工作目录, 不让主从节点公用同一份 RDB 与 AOF 文件
dir /root/redis-conf/slave1
# 代表这个从节点的主节点的 ip 为 127.0.0.1, 端口为 6379
slaveof 127.0.0.1 6379
# 从节点 2
port 6381
dir /root/redis-conf/slave2
slaveof 127.0.0.1 6379
- 分别启动三个节点
root@server:~# redis-server /etc/redis/redis.conf
root@server:~# redis-server redis-conf/slave1.conf
root@server:~# redis-server redis-conf/slave2.conf
可以看到三个节点已经启动了:
现在主从结构已经配置完成了, 接下来验证一下:
-
在主节点上设置一个 key
-
打开一个从节点, 查看这个 key
可以看到, 主节点和从节点中的数据已经成功同步.
-
尝试在从节点中写入数据
发现就如上面所说的一样, 从节点不支持写入数据.
查看主从结构信息
在客户端中可以使用 info replication 命令来查看主从结构信息
接下来解释一下其中的一些信息:
-
role: 这个客户端连接的服务器主要扮演的角色, master 为主节点, slave 为从节点.
-
connected_slave: 表示这个主节点连接的从节点个数.
-
offset: 从节点和主节点之间同步数据的进度.
-
lag: 延迟, 表示主节点和从节点之间的网络延迟
-
master_replid: 表示主节点的身份标识, 从节点会记录这个身份标识, 表示它所连接的主节点是哪个
断开主从结构以及修改主从结构信息
- 在客户端中, 使用 slaveof no one 来断开当前的主从关系
这里在 6380 从节点中来执行:
接着再查看主从结构信息:
这个节点的 role 从 slave 变为了 master, 可以发现, 当从节点断开主从复制关系之后, 它就不再从属于其他节点了. 但是里面已经有的数据是不会抛弃的, 但是, 后续主节点对数据进行修改, 这个断开的从节点就不能同步数据了.
- 也可以使用 slaveof {masterHost} {masterPort} 这个命令来使这个节点变为其它节点从节点
可以看到, 当前的 6380 节点的主节点已经变为了 6381, 与之前相比, 结构已经发生了改变:
注意: 上述通过 slaveof 命令来修改主从结构, 但是这种修改只是临时性的如果重新启动了 redis 服务器, 仍然会按照最初在配置文件中的设定来建立主从关系.
而且, 上述将 6381 的主节点变为了 6380, 此时 6380 看似既是 6379 的从节点, 又是 6381 的主节点, 但是它作为从节点还是不能够修改数据的, 因为一旦在从节点上修改数据, 主节点是感知不到的, 这样数据就不一致了.
主从复制的基本流程
主从复制的基本流程如下图所示:
当某个客户端执行了 slaveof 操作, 就会触发主从复制的流程:
- 先保存主节点的 ip 和 端口, 使用变量在内存中进行保存.
- 主节点和从节点建立 tcp 连接.
- 从节点向主节点发送 ping 命令, 主节点返回 pong, 是为了验证主节点是否能够正常工作.
- redis 主节点如果开启了密码, 就需要进行权限验证, 验证成功才会进行下一步.
- 进行全量同步, 将目前主节点中的所有数据同步到从节点.
- 进行增量同步, 后续新增的数据会持续同步到从节点.
第二步的时候主节点和从节点建立 tcp 连接的过程中, 会进行三次握手, 其中三次握手的一个功能就是验证通信双方是否能够正常发送和接收数据, 那么为什么还需要第三步从节点向主节点发送 ping 来验证主节点是否正常工作呢?
如果将主从复制的流程比作高速公路, 那么 tcp 三次握手只是为了确定这条高速的路是否能通, 三次握手是站在系统层面的角度. 而从节点向主节点发送 ping 命令是为了确定主节点是否能工作, 也就是车是否能跑, 这是站在应用层的角度.
数据同步
redis 提供了 psync 命令, 完成数据同步的过程. psync 命令不需要手动执行, redis 服务器会在建立好主从同步关系后从节点自动执行 psync (从节点主动从从主节点拉取数据)
psync 的语法格式:
PSYNC replicationid offset
replid
其中, replicationid(replid) 是主节点生成的, 主节点在启动的过程中就会生成 replid, 并且每次重启生成的 replid 都是不同的. 从节点和主节点建立了复制关系, 就会从主节点这边获取到 replid.
可以使用 info replication 命令来获取到当前 replid 的值:
可以看到, 执行这个命令之后, 就可以查看当前主从结构中主节点的 replid.
但是从上图可以看到, replid 有两份, 其中还有一份 replid2. 这个 replid2 的数据全是 0, 是用来做什么的呢?
- 其实, replid2 在一般情况下是用不上的, 什么时候会用到呢? 假设一个情况: 有一个主节点 A 和一个从节点 B, B 记录着 A 的 replid, 如果 A 和 B 的通信过程中出现了网络抖动, B 就可能认为 A 挂了, 这时 B 就会晋升成主节点, 而主节点会拥有一个唯一的 replid, 当 B 晋升之后会给自己生成一个 replid, 但是 B 不会将 A 的 replid 丢弃, 而是用 replid2 来保存 A 的 replid, 目的就是为了当后续网络稳定了, B 可以根据 replid2 来重新与 A 组成主从关系(需要手动干预, 后续介绍的哨兵机制可以自动完成这个过程).
offset
上述信息中, 还有一个重要的字段: offset (偏移量). 主节点和从节点上都会维护这个偏移量(偏移量是一个整数).
- 主节点上的偏移量是由主节点上收到的**修改命令(不包括查询)**的字节数构成, 每当主节点收到修改命令, 主节点会把这些命令的字节数累加到 offset 的值里.
- 从节点的偏移量就描述了现在从节点这里的数据同步到了哪里.
- 如果主节点和从节点上的偏移量值相同了, 就代表主节点和从节点上面的数据保持一致了.
可以看到, replid 和 offset共同描述了一个数据集合. 如果发现两个机器中, replid 相同并且 offset 也相同, 那么就可以认为这两台 redis 机器上存储的数据是完全相同的.
psync 的运行流程
从上图可以看到, 从节点向主节点发送 psync 命令, 主节点会根据 psync 的参数和自身的数据情况来响应.
- psync 可以从主节点获取全量数据, 也可以获取一部分数据, 主要依靠的是 offset 这里的进度, offset 写作 -1, 就是获取全量数据, 写作具体的正整数, 则是从当前的偏移量位置来获取.
获取所有的数据是最稳妥的, 但是也是比较低效的, 如果从节点之前已经从主节点这里复制过一部分数据了, 就只需要把之前没复制过的新数据复制过来即可.
- 并不是说从节点索要哪部分数据, 主节点就一定给哪部分数据, 主节点会自行判定看当前是否方便给部分数据, 如果不方便就只能给全量数据了.
根据上图来看, 从节点执行 psync 命令之后, 主节点会根据情况返回如下三个响应:
- FULLRESYNC: 全量数据同步.
- CONTINUE: 增量数据同步.
- ERR: 版本不支持, 比较老的版本的 redis 不支持 psync 命令 (可以使用 sync 命令, 但是 sync 会阻塞 redis 服务器, 而 psync 不会).
什么时候会进行全量复制:
- 首次和从节点进行数据同步.
- 主节点不方便进行部分复制.
什么时候会进行部分复制:
- 从节点已之前已经从主节点上复制过数据了, 但是因为网络抖动或者从节点重启了.
- 从节点需要重新从主节点这边同步数据, 此时判断是否只需要同步一小部分数据(大部分数据都是一致的情况).
全量复制的流程
-
从节点发送 psync 命令给主节点进⾏数据同步, 由于是第⼀次进⾏复制, 从节点没有主节点的运
行 ID 和复制偏移量, 所以发送psync ? -1. -
主节点根据命令, 解析出要进行全量复制, 回复 +FULLRESYNC 响应.
-
从节点接收主节点的运行信息并保存.
-
主节点执行 bgsave 进行 RDB 文件的持久化.
-
主节点发送 RDB 文件给从节点, 从节点保存 RDB 数据到本地硬盘.
-
主节点将从生成 RDB 到接收完成期间执行的写命令写入到缓冲区, 等从节点保存完 RDB 文件后, 主节点再将缓冲区内的数据补发给从节点, 补发的数据仍然按照 RDB 的二进制格式追加写入到收到的 RDB 文件中, 保持主从的一致性.
-
从节点清空自身原有的旧数据.
-
从节点加载 RDB 文件得到与从节点一致的数据.
-
如果从节点加载 RDB 完成之后, 并且开启了 AOF 功能, 它会进行 bgrewriteaof, 得到最近的 AOF 文件.
在第五步中, 主节点传输 RDB 文件给从节点, 从节点会保存 RDB 数据到硬盘中, 无疑会产生一部分开销. redis 主节点再进行全量复制的时候, 也支持 “无硬盘模式” (diskless). 主节点生成 RDB 的二进制数据, 不是直接保存到文件中了, 而是直接进行网络传输了(省下了一系列读硬盘和写硬盘的操作). 从节点之前也是把收到的 RDB 数据写入到硬盘中然后再加载, 现在也可以省略这个过程, 直接把收到的数据进行加载.
==即使引入了无硬盘模式, 整个操作仍然是比较重量, 比较耗时的. 网络传输大规模数据是没法省的, 反而相比于网络传输, 读写硬盘反而只占了小头.
部分复制的流程
上述讲的全量复制, 可以看出开销是很大的. 有些时候, 从节点本身已经持有了主节点的绝大部分数据, 这时候就不太需要进行全量复制了.
从节点持有主节点数据的情况: 比如出现网络抖动, 主节点最近修改的数据可能无法及时同步过来. 更严重的是从节点已经感知不到主节点了(进一步从节点可能会升级成主节点). 当网络抖动恢复了, 此时就可以让从节点和主节点重新建立联系, 当从节点和主节点重新连接之后, 就需要进行数据同步, 此时就可以使用部分复制.
- 当主从节点之间出现网络中断的时候, 如果超过 repl-timeout, 主节点会认为从节点故障并中断复制连接.
- 主从连接中断期间主节点依然响应命令, 但这些复制命令都因为网络中断无法及时发送给从节点, 所以暂时将这些命令滞留在复制积压缓冲区中.
- 当主从节点网络恢复后, 从节点再次连上主节点.
- 从节点将之前保存的 replid 和 offset 作为 psync 的参数发送给主节点, 请求进行部分复制.
- 主节点收到 psync 请求后, 进行必要的验证. 随后根据 offset 去复制积压缓冲区查找合适的数据并响应 +CONTINUE 给从节点.
- 主节点将需要从节点同步的数据发送给从节点, 最终完成一致性.
关于积压缓冲区:
积压缓冲区就是内存中的一个简单的队列, 会记录最近一段时间修改的数据, 这个队列总量有限, 随着时间的推移会把之前的旧数据逐渐删掉. 此时再进行部分复制的时候, 主节点发现要同步的数据已经被删除了, 这时就只能进行全量复制了.
实时复制的流程
从节点已经从主节点同步好数据了(此时从节点的数据和主节点的数据一致了). 但是之后, 主节点这边会源源不断的收到寻得修改数据的请求, 主节点的数据就会随之改变, 也需要能够同步给从节点.
从节点和主节点之间会建立 TCP 长连接, 然后主节点把自己收到的修改数据的请求通过上述连接, 发送给从节点, 主节点再根据这些修改请求, 修改内存中的数据.
在进行实时复制的时候, 要保证连接处于可用状态. redis 是使用心跳包机制来保证.
主节点默认每隔 10s 给从节点发送一个 ping 命令, 从节点收到就返回 pong.
从节点默认每隔 1s 就给从节点发起一个特定的请求, 就会上报当前从节点复制数据的进度(offset)
小结
主从复制解决的问题:
单点问题
- 单个 redis 节点可用性不高
- 性能有限
主从复制的特点:
- redis 通过复制功能实现主节点的多个副本
- 主节点用来写, 从节点用来读, 降低主节点的访问压力
- 复制分为全量复制, 部分复制和实时复制
- 主从节点之间通过心跳包机制来保证主从节点通信正常和数据一致性
主从配置过程:
- 主节点配置文件不需要改动
- 从节点在配置文件中加入 slaveof 主节点ip 主节点端口即可
主从复制缺点:
- 从机多了, 复制数据的延时会很明显
- 主机挂了, 从机不会升级成主机, 只能通过人工干预的方式恢复