无题
在上一节中,我们详细分析了 Redis Cluster 中两个节点之间握手的核心流程,这也是搭建 Redis Cluster 的第一步:让各个 Cluster 节点之间感知到彼此的存在。
前面提到,Redis Cluster 中每个 Master 节点都会有至少一个 Slave 节点,所以设置 Redis Cluster 中每个节点的主从角色就是启动 Redis Cluster 第二步要做的事情。
另外,Redis Cluster 会将它所有的键值对分散到 16384 个 slot 槽位中,而这 16384 个槽位会分配到不同 Master 节点进行管理 ,如何分配 slot 就是启动 Redis Cluster 第三步要做的事情。
这一节,我们就先来看看配置 Cluster 主从关系以及 slot 的分配是如何实现的。
集群配置在 Redis Cluster 中,各个节点之间相互握手之后,我们还需要进行一些配置操作才能得到一个真正可用的 Redis Cluster 集群:一是设置各个节点之间的主从关系,另一个是为 Master 节点分配 slot。
配置主从关系在 Redis Clu ...
无题
在上一节中,我们已经详细介绍了 Redis Cluster 中集群配置的两个方面,一个是 Cluster 节点之间的主从关系配置,另一个是 slot 槽位分配的问题。之后,分析了 clusterCron() 周期性发送 PING 消息以及PING 消息接收方解析 PING 消息的逻辑,其中重点介绍了对 clusterMsgDataGossip 部分的解析和处理。
在 Redis Cluster 完成前面两节介绍的启动流程之后,就可以正常对外提供服务了。在提供服务的期间,Redis Cluster 中可能会因为网络、磁盘、内存等各种方面的问题,导致其中某些 Master 节点出现不可用的情况。这个时候,就需要 Redis Cluster 进行自动故障转移,将 Slave 节点提升为 Master 节点继续对外提供服务,保证整个 Redis Cluster 集群的高可用。
Redis Cluster 中的 failover 分为自动 failover 和手动 failover,自动 failover 是由 Redis Cluster 通过自身的探活机制发现宕机而触发的,手动 failove ...
无题
在前面的文章中,我们已经完整介绍了 Redis Cluster 启动流程,以及完整的 failover 流程,对应的核心实现和关键函数也进行了说明和介绍。这一节,我们再来讨论一下 Redis Cluster 在 Slave 漂移以及数据迁移方面的功能。
Slave 节点漂移在 clusterCron() 这个周期性任务中,除了前面介绍的定时发送 PING 消息、触发 failover 操作之外,还会检查 Master 的单点问题。所谓“单点 Master 问题”意思就是:一个 Master 节点下没有任何可用的 Slave 节点存在,如果此时 Master 节点发生了故障,整个 Redis Cluster 将进入不可用的状态。
为了解决这个问题,Redis Cluster 提供了 Slave 节点漂移的功能,redis.conf 配置文件中的 cluster-allow-replica-migration 配置项为该功能的开关。Slave 节点漂移的核心原理是:当 Redis Cluster 发现单点 Master 的时候,会从其他拥有多个可用 Slave 的 Master 节点那里, ...
无题
分析完写入 RDB 文件的格式以及 RDB 持久化的核心实现之后,我们回到触发 RDB 持久化的地方会发现,除了 SAVE 命令之外,其他 rdbSave() 函数调用都是来自 rdbSaveBackground() 函数,如下图调用栈所示:
rdbSaveBackground() 函数的核心在于,使用 fork() 调用创建一个子进程,并在子进程中调用rdbSave() 函数,完成后台的 RDB 持久化操作。在 redisServer 中有一个 child_pid 字段,它用来记录当前 Redis 创建的子进程 id,这个子进程可以是用来进行 RDB 持久化的,也可以用来做 AOF Rewrite 操作的,或是其他 Module 需要的后台操作。但是,Redis 同一时刻只能有一个子进程,这个子进程的 id 会被记录到 child_pid 字段中。
创建 RDB 子进程下面我们就先来看看用于 RDB 持久化的子进程是怎么创建出来的,下面是 rdbSaveBackground() 函数的核心逻辑。
它首先会调用 hasActiveChildProcess() 函数来检查 server ...
无题
前文我们已经详细介绍了 Redis 中 RDB 持久化的相关内容,本节我们开始介绍一下 Redis 中另一种持久化方式 —— AOF 持久化。
通过前面章节的介绍我们知道,RDB 是一个类似于快照的持久化方式,它会一次性将 Redis 内存中的全部数据写入到 RDB 文件中。AOF(Append Only File)持久化则是类似于增量的持久化,其核心思路是将 Redis 执行过的每条修改命令都保存到 AOF 文件中,从而实现持久化效果。当故障恢复的时候,Redis 可以根据 AOF 文件回放曾经执行过的每一条命令,这样的话,Redis 中的数据也就恢复到故障前的状态了。
在实际生产环境中,一般会使用 AOF + RDB 的混合持久化方案来达到最高效的持久化效果,这个方案是 RDB 定时全量持久化,这样在故障恢复时就可以将 Redis 恢复到 RDB 创建时的状态,然后回放 RDB 创建时间点到故障时间点之间的 AOF 日志,将 Redis 从 RDB 文件快照下的状态恢复到故障时间点的状态。
这样做主要从两个方面考虑。
第一方面是:RDB 持久化这种全量持久化方式,是个比较耗时的操 ...
无题
通过上一节的介绍,我们了解了 AOF 日志从生产到写入缓冲区的过程,也分析了 AOF 日志的格式,这里有两个额外的知识点需要补充说明一下。
第一个点是,当执行的是 EXPIRE 这种设置过期时间的命令,如果 AOF 是把 EXPIRE 指定的过期时间记录下来,在回放的时候,是不是就给 Key 设置了一个错误的过期时间呢?这个问题的答案需要小伙伴们回顾一下第 36 讲《命令解析篇:通用命令与 String 命令实现解析》中介绍的,expireGenericCommand() 函数中的一个细节,在设置完 Key 的过期时间之后,expireGenericCommand() 就已经将 client->argv 中记录的命令和参数进行了改写,它会将命令改成 PEXPIREAT 命令,并将过期时间归一化成毫秒级时间戳。同理, SET…EX|PX 这个带过期时间的复合命令,也会对命令进行改写,也是统一改写 SET…PXAT,过期时间归一化成毫秒级时间戳。
第二个点是,在介绍 Key 过期以及内存淘汰的时候,我们知道这两个功能是会将一部分 Key 删除掉的,这个时候需要产生 DEL 或者 UN ...
无题
通过前文的介绍我们知道,Redis 在开启 AOF 持久化功能之后,会将修改命令写入到磁盘上的 AOF 文件。随着 Redis 运行时间越久,就会有越来越多的命令追加 AOF 文件中,AOF 文件的大小也会不断膨胀,如果之后在某个时间点,要使用 AOF 文件进行恢复,就会读取很多无用的命令,导致耗时较长。
下图举了个例子,Redis 依次收到了 SET Key1 Value1 、SET Key1 Value2 、SET Key1 Value3 、SET Key1 Value4 这四条命令,相应地,在 AOF 文件中就会记录这 4 条命令,如下图最左边这一栏所示。在这 4 条命令的执行过程中,Redis 中 Key1 对应的 Value 值也在不断发生变化,如果下图红色那一栏所示。如果我们在 SET Key1 Value4 这条命令执行完之后,使用这个 AOF 文件进行数据恢复,这里的前三条 SET 命令其实都是无效的,因为执行或不执行这些语句,都不会影响最终的恢复结果。
为了解决这一问题,Redis 会定期对 AOF 进行压缩,这一操作被称为 AOF Rewrite,其核心原理是将 ...
无题
在大流量、高并发的场景中,我们一般不会只有一个单点作为存储,而是把存储做成一个分布式的系统,例如,我们常见的 MySQL 主从结构、Redis 主从结构、Redis Cluster、Memcache 集群或者 TiKV 这种基于 Raft 协议的集群。
Redis 有多种集群搭建方式,比如,主从模式、哨兵模式、Cluster 模式。本模块,我们就重点来介绍一下 Redis 主从模式的相关内容。
在使用 Redis 主从复制模式的时候,一般会搭建多个从库(Slave),从库只支持读请求的处理,用来分摊主库(Master)的读压力,主库(Master)只有一个,只专注于处理写请求,或者同时支持读写,这样就可以实现读写分离,降低主库的读压力,也实现了读请求在各个 Redis 节点之间的负载均衡。这样,我们就得到了 Redis 主从模式的核心架构,如下图所示:
正如前面所说,Redis 主从模式还解决了单点的问题。Redis 主库在进行修改操作的时候,会把相应的写入命令近乎实时地同步给从库,从库回放这些命令,就可以保证自己的数据与主库保持一致。那么,当主库发生宕机的时候,我们就可以将一个从库 ...
无题
介绍完主从复制的核心原理之后,从这节开始,我们将介绍 Redis 主从复制的核心实现。在上一节中提到,Redis 主从结构最开始,是由从库向主库发起建连请求的,所以这里就先以从库视角来看看主从复制的整个流程。
从库建连设置主库地址明确了从库是主从复制的主动发起方之后,我们再来看看从库是如何确认自己要连接哪个主库的,下面有两种设置主库的方式。
一种是从库在配置文件(或是启动参数)中添加了 replicaof 配置或者 slaveof 配置,replicaof 出现在 Redis 5.0 版本中,用于替换 slaveof 配置,slaveof 目前已经被标记为废弃 。在 Redis 从库启动过程中,loadServerConfigFromString() 函数中会解析 redis.conf 文件(以及启动参数)中的 replicaof(或 slaveof)配置,将主库的网络地址记录到 redisServer.masterhost 和 redisServer.masterport 中。
另一种是在从库启动之后,通过客户端向 Redis 服务发送 replicaof(或者 slaveof) ...
无题
通过上一节的分析我们知道,主从建连之后会一系列握手操作,这里面最关键的一步就是从库向主库发送 PSYNC 命令,其中会携带从库当前的 Replication ID 和 Replication Offset。这里紧接上文,继续介绍从库对 PSYNC 响应的处理。
当主库返回 +CONTINUE 响应的时候,表示进行部分同步,从库会直接进入 REPL_STATE_CONNECTED 状态,主从握手的流程也就结束了,后续会进入正常的主从复制流程。
当主库返回 +FULLRESYNC 响应时,从库就要准备与主库进行全量同步了,下面是从库需要做的准备工作。
从库首先会创建一个名为 temp-{秒级时间戳}.{进程ID}.rdb 的临时 RDB 文件,然后将这个文件名称以及对应的文件描述符记录到 redisServer.repl_transfer_tmpfile 字段和 repl_transfer_fd 字段中。
监听主从连接上的可读事件,等待主库发送 RDB 数据,相应的回调为 readSyncBulkPayload() 函数。
最后,从库会将 re ...
