目录
[调整repl_backlog_buffer 缓冲区](#调整repl_backlog_buffer 缓冲区)
[第三轮考察:ID 号小的从节点胜出](#第三轮考察:ID 号小的从节点胜出)
[7. MOVED错误与ASK错误](#7. MOVED错误与ASK错误)
[8. Redis Cluser是如何保证高可用的](#8. Redis Cluser是如何保证高可用的)
一.主从复制
1.概述
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave)。
- 数据的复制是单向的,只能由主节点到从节点
- 默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),从节点也可以拥有从节点,但一个从节点只能有一个主节点
- 主节点可写可读,主要负责写操作,从节点只能进行读操作
- 主机shutdown后,从机数据可以正常使用,即仍能提供读,等待主机重启动归来
2.主从架构相比于单点架构的优势
- 数据冗余和高可用性:为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。即使有其中一台服务器宕机,其他服务器依然可以继续提供服务。
- 负载均衡提高读取性能:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
3.主从复制原理和工作流程
第一次同步
当从服务器 salve 启动后,会与主服务器进行第一次同步。主从服务器间的第一次同步的过程可分为三个阶段:
- 第一阶段是建立链接、协商同步;
- 第二阶段是主服务器同步数据给从服务器;
- 第三阶段是主服务器发送新写操作命令给从服务器。

第一阶段:建立链接、协商同步
- 执行了 replicaof 命令后,从服务器就会给主服务器发送 psync 命令,表示要进行数据同步。psync 命令包含两个参数,分别是主服务器的 runID 和复制进度 offset。
- 主服务器收到 psync 命令后,会用 FULLRESYNC 作为响应命令返回给对方。并且这个响应命令会带上两个参数:主服务器的 runID 和主服务器目前的复制进度 offset。从服务器收到响应后,会记录这两个值。
FULLRESYNC 响应命令的意图是采用全量复制的方式,也就是主服务器会把所有的数据都同步给从服务器。
第二阶段:主服务器同步数据给从服务器
- 接着,主服务器会执行 bgsave 命令来生成 RDB 文件,然后把文件发送给从服务器。
- 从服务器收到 RDB 文件后,会先清空当前的数据,然后载入 RDB 文件。
这里有一点要注意,主服务器生成 RDB 这个过程是不会阻塞主线程的,因为 bgsave 命令是产生了一个子进程来做生成 RDB 文件的工作,是异步工作的,这样 Redis 依然可以正常处理命令。
但是,这期间的写操作命令并没有记录到刚刚生成的 RDB 文件中,这时主从服务器间的数据就不一致了。那么为了保证主从服务器的数据一致性,主服务器在下面这三个时间间隙中将收到的写操作命令,写入到 replication buffer 缓冲区里:
- 主服务器生成 RDB 文件期间;
- 主服务器发送 RDB 文件给从服务器期间;
- 「从服务器」加载 RDB 文件期间;
第三阶段:主服务器发送新写操作命令给从服务器
- 在主服务器生成的 RDB 文件发送完,从服务器收到 RDB 文件后,丢弃所有旧数据,将 RDB 数据载入到内存。完成 RDB 的载入后,会回复一个确认消息给主服务器。
- 接着,主服务器将 replication buffer 缓冲区 里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区 里发来的命令,这时主从服务器的数据就一致了。
至此,主从服务器的第一次同步的工作就完成了。
基于长连接的命令传播
主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 连接。而且这个连接是长连接的,目的是避免频繁的 TCP 连接和断开带来的性能开销。
后续主服务器可以通过这个连接继续将写操作命令传播给从服务器,然后从服务器执行该命令,使得与主服务器的数据库状态相同。

Redis 通过互相的 ping-pong 心态检测机制来 判断节点是否正常工作,如果有一半以上的节点去 ping 一个节点的时候没有 pong 回应,集群就会认为这个节点挂掉了,会断开与这个节点的连接。
Redis 主从节点发送的心态间隔是不一样的,而且作用也有一点区别:
- Redis 主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态,可通过参数repl-ping-slave-period控制发送频率。
- Redis 从节点每隔 1 秒发送 replconf ack{offset} 命令,给主节点上报自身当前的复制偏移量,目的是为了:
- 实时监测主从节点网络状态;
- 上报自身复制偏移量, 检查复制数据是否丢失, 如果从节点数据丢失, 再从主节点的复制缓冲区中拉取丢失数据。
分摊主服务器的压力
问题引入
在前面的分析中,我们可以知道主从服务器在第一次数据同步的过程中,主服务器会做两件耗时的操作:生成 RDB 文件 和 传输 RDB 文件。
主服务器是可以有多个从服务器的,如果从服务器数量非常多,而且都与主服务器进行全量同步的话,就会带来两个问题:
- 由于是通过 bgsave 命令来生成 RDB 文件的,那么主服务器就会忙于使用 fork() 创建子进程,如果主服务器的内存数据非大,在执行 fork() 函数时是会阻塞主线程的,从而使得 Redis 无法正常处理请求;
- 传输 RDB 文件会占用主服务器的网络带宽,会对主服务器响应命令请求产生影响。
这种情况就好像,刚创业的公司,由于人不多,所以员工都归老板一个人管,但是随着公司的发展,人员的扩充,老板慢慢就无法承担全部员工的管理工作了。
"经理"角色的从服务器
要解决这个问题,老板就需要设立经理职位,由经理管理多名普通员工,然后老板只需要管理经理就好。
Redis 也是一样的,从服务器可以有自己的从服务器,我们可以把拥有从服务器的从服务器当作经理角色,它不仅可以接收主服务器的同步数据,自己也可以同时作为主服务器的形式将数据同步给从服务器,但是它仍然不可以执行写操作,组织形式如下图:

通过这种方式,主服务器生成 RDB 和传输 RDB 的压力可以分摊到充当经理角色的从服务器。
增量复制
问题引入
主从服务器在完成第一次同步后,就会基于长连接进行命令传播。如果主从服务器间的网络连接断开了,那么就无法进行命令传播了,这时从服务器的数据就没办法和主服务器保持一致了,客户端就可能从「从服务器」读到旧的数据。

- 在 Redis 2.8 之前,如果主从服务器在命令同步时出现了网络断开又恢复的情况,从服务器就会和主服务器重新进行一次全量复制,很明显这样的开销太大了,必须要改进一波。
- 所以,从 Redis 2.8 开始,网络断开又恢复后,从主从服务器会采用增量复制的方式继续同步,也就是只会把网络断开期间主服务器接收到的写操作命令,同步给从服务器。
网络恢复后的增量复制过程如下图:
增量复制过程
- 从服务器在恢复网络后,会发送 psync 命令给主服务器,此时的 psync 命令里的 offset 参数不是 -1;
- 主服务器收到该命令后,然后用 CONTINUE 响应命令告诉从服务器接下来采用增量复制的方式同步数据;
- 然后主服务将主从服务器断线期间,所执行的写命令发送给从服务器,然后从服务器执行这些命令。
主服务器怎么知道要将哪些增量数据发送给从服务器的
答案藏在这两个东西里:
- repl_backlog_buffer: 是一个「环形」缓冲区,用于主从服务器断连后,从中找到差异的数据;在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到 repl_backlog_buffer 缓冲区里,因此 这个缓冲区里会保存着最近传播的写命令。
- **replication offset:**标记上面那个缓冲区的同步进度,主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 来记录自己「写」到的位置,从服务器使用 slave_repl_offset 来记录自己「读」到的位置。
网络断开后,当从服务器重新连上主服务器时,从服务器会通过 psync 命令将自己的复制偏移量 slave_repl_offset 发送给主服务器,主服务器根据自己的 master_repl_offset 和 slave_repl_offset 之间的差距,然后来决定对从服务器执行哪种同步操作:
- 如果判断出从服务器要读取的数据还在 repl_backlog_buffer 缓冲区里,那么主服务器将采用增量同步的方式;
- 相反,如果判断出从服务器要读取的数据已经不存在 repl_backlog_buffer 缓冲区里,那么主服务器将采用全量同步的方式。
当主服务器在 repl_backlog_buffer 中找到主从服务器差异(增量)的数据后,就会将增量的数据写入到 replication buffer 缓冲区,这个缓冲区我们前面也提到过,它是缓存将要传播给从服务器的命令。

repl_backlog_buffer 缓行缓冲区的默认大小是 1M,并且由于它是一个环形缓冲区,所以当缓冲区写满后,主服务器继续写入的话,就会覆盖之前的数据。因此,当主服务器的写入速度远超于从服务器的读取速度,缓冲区的数据一下就会被覆盖。
因此,为了避免在网络恢复时,主服务器频繁地使用全量同步的方式,我们应该调整下 repl_backlog_buffer 缓冲区大小,尽可能的大一些,减少出现从服务器要读取的数据被覆盖的概率,从而使得主服务器采用增量同步的方式。
调整repl_backlog_buffer 缓冲区
repl_backlog_buffer 最小的大小可以根据这面这个公式估算。

- second 为从服务器断线后重新连接上主服务器所需的平均 时间(以秒计算)。
- write_size_per_second 则是主服务器平均每秒产生的写命令数据量大小。
举个例子,如果主服务器平均每秒产生 1 MB 的写命令,而从服务器断线之后平均要 5 秒才能重新连接主服务器。那么 repl_backlog_buffer 大小就不能低于 5 MB,否则新写地命令就会覆盖旧数据了。
当然,为了应对一些突发的情况,可以将 repl_backlog_buffer 的大小设置为此基础上的 2 倍,也就是 10 MB。
4.主从架构的缺点
在 Redis 的主从架构中,由于主从模式是读写分离的,如果主节点(master)挂了,那么将没有主节点来服务客户端的写操作请求,也没有主节点给从节点(slave)进行数据同步了。
这时如果要恢复服务的话,需要人工介入,选择一个「从节点」切换为「主节点」,然后让其他从节点指向新的主节点,同时还需要通知上游那些连接 Redis 主节点的客户端,将其配置中的主节点 IP 地址更新为「新主节点」的 IP 地址。
二.哨兵机制
1.概念
在 Redis 的主从架构中,如果主节点(master)挂了,必须要人工介入。因此我们需要一个能监控「主节点」状态 的节点;当发现主节点挂了 ,它能根据投票数自动将一个「从节点」切换为「主节点」,并且把新主节点的相关信息通知给从节点和客户端,这就是哨兵机制。
2.哨兵的作用
- 主从监控:监控主从redis库运行是否正常
- 故障转移:如果Master异常,则会进行主从切换,将其中一个Slave作为新Master
- 消息通知:哨兵可以将故障转移的结果发送给客户端
- 配置中心:客户端通过连接哨兵来获得当前Redis服务的主节点地址
注:哨兵只做监控和维护集群,不存放数据
3.哨兵如何判断节点真的故障
哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。

如果主节点或者从节点没有在规定的时间内响应哨兵的 PING 命令,哨兵就会将它们标记为「主观下线」。这个「规定的时间」是配置项 down-after-milliseconds 参数设定的,单位是毫秒。
主观下线
「主观下线 」是针对于单个的哨兵来讲的,因为网络拥堵等原因很可能会误判,所以为了减少误判的情况,哨兵在部署的时候不会只部署一个节点,而是用多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断,就可以就可以避免单个哨兵因为自身网络状况不好,而误判主节点下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
客观下线
有主观就有客观,当一个哨兵判断主节点为「主观下线」后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。

当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为**「客观下线」**。
例如,现在有 3 个哨兵,quorum 配置的是 2,那么一个哨兵需要 2 张赞成票,就可以标记主节点为"客观下线"了。这 2 张赞成票包括哨兵自己的一张赞成票和另外两个哨兵的赞成票。
注:quorum 的值一般设置为哨兵个数的二分之一加1,例如 3 个哨兵就设置 2。而且哨兵节点的数量应该是奇数。
哨兵判断完主节点客观下线后,哨兵就要开始在多个「从节点」中,选出一个从节点来做新主节点。
4.由哪个哨兵进行主从故障转移
我们之前提到过,采用哨兵机制那么一定要部署哨兵集群,那么当master挂了之后,由谁去进行主从故障转移呢?一起去吗?很显然不是。
当主节点被判断客观下线以后,各个哨兵节点会进行协商先选举出一个领导者哨兵节点并由该领导者节点进行failover (故障迁移)
谁是候选者
哪个哨兵节点判断主节点为「客观下线」,这个哨兵节点就是候选者,所谓的候选者就是想当 Leader 的哨兵。
举个例子,假设有三个哨兵。当哨兵 B 先判断到主节点「主观下线后」,就会给其他实例发送 is-master-down-by-addr 命令。接着,其他哨兵会根据自己和主节点的网络连接情况,做出赞成投票或者拒绝投票的响应。

当哨兵 B 收到赞成票数达到哨兵配置文件中的 quorum 配置项设定的值后,就会将主节点标记为「客观下线」,此时的哨兵 B 就是一个Leader 候选者。
如何选出Leader
候选者会向其他哨兵发送命令,表明希望成为 Leader 来执行主从切换,并让所有其他哨兵对它进行投票。选举使用的算法是Raft算法;Raft算法的基本思路是先到先得: 即在一轮选举中,哨兵B向A发送成为领导者的申请,如果A没有同意过其他哨兵,则会同意B成为领导者。
每个哨兵只有一次投票机会,如果用完后就不能参与投票了,可以投给自己或投给别人,但是只有候选者才能把票投给自己。
在投票过程中,任何一个「候选者」,要满足两个条件 才会被选举为Leader:
- 第一,拿到半数以上的赞成票;
- 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。
举个例子,假设哨兵节点有 3 个,quorum 设置为 2,那么任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以选举成功了。如果没有满足条件,就需要重新进行选举。
如果某个时间点,刚好有两个哨兵节点判断到主节点为客观下线,每位候选者都会先给自己投一票,然后向其他哨兵发起投票请求。如果投票者先收到「候选者 A」的投票请求,就会先投票给它,如果投票者用完投票机会后,收到「候选者 B」的投票请求后,就会拒绝投票。这时,候选者 A 先满足了上面的那两个条件,所以「候选者 A」就会被选举为 Leader。
5.主从故障转移的过程
在哨兵集群中通过投票的方式,选举出了哨兵 leader 后,就可以进行主从故障转移的过程了,如下图:

主从故障转移操作包含以下四个步骤:
- 第一步:在已下线主节点(旧主节点)属下的所有「从节点」里面,挑选出一个从节点,并将其转换为主节点。
- 第二步:让已下线主节点属下的所有「从节点」修改复制目标,修改为复制「新主节点」;
- 第三步:将新主节点的 IP 地址和信息,通过「发布者/订阅者机制」通知给客户端;
- 第四步:继续监视旧主节点,当这个旧主节点重新上线时,将它设置为新主节点的从节点;
步骤一:选出新主节点
故障转移操作第一步要做的就是在已下线主节点属下的所有「从节点」中,挑选出一个状态良好、数据完整的从节点,然后向这个「从节点」发送 SLAVEOF no one 命令,将这个「从节点」转换为「主节点」。
挑选过程如下:
- 第一轮考察:哨兵首先会根据从节点的优先级来进行排序,优先级越小排名越靠前,
- 第二轮考察:如果优先级相同,则查看复制的下标,哪个从「主节点」接收的复制数据多,哪个就靠前。
- 第三轮考察:如果优先级和下标都相同,就选择从节点 ID 较小的那个。

第一轮考察:优先级最高的从节点胜出
Redis 有个叫 slave-priority 配置项,可以给从节点设置优先级。每一台从节点的服务器配置不一定是相同的,我们可以根据服务器性能配置来设置从节点的优先级。
比如,如果 「 A 从节点」的物理内存是所有从节点中最大的, 那么我们可以把「 A 从节点」的优先级设置成最高。这样当哨兵进行第一轮考虑的时候,优先级最高的 A 从节点就会优先胜出,于是就会成为新主节点。
第二轮考察:复制进度最靠前的从节点胜出
如果在第一轮考察中,发现优先级最高的从节点有两个,那么就会进行第二轮考察,比较两个从节点哪个复制进度。
之前我们在讲主从架构的时候就提到过复制进度:主从架构中,主节点会将写操作同步给从节点,在这个过程中,主节点会用 master_repl_offset 记录当前的最新写操作在 repl_backlog_buffer 中的位置(如下图中的「主服务器已经写入的数据」的位置),而从节点会用 slave_repl_offset 这个值记录当前的复制进度(如下图中的「从服务器要读的位置」的位置)。

如果某个从节点的 slave_repl_offset 最接近 master_repl_offset,说明它的复制进度是最靠前的,于是就可以将它选为新主节点。
第三轮考察:ID 号小的从节点胜出
如果在第二轮考察中,发现有两个从节点优先级和复制进度都是一样的,那么就会进行第三轮考察,比较两个从节点的 ID 号, ID 号是用来唯一标识从节点的,ID 号小的从节点胜出。
在选举出从节点后,哨兵 leader 向被选中的从节点发送 SLAVEOF no one 命令,让这个从节点解除从节点的身份,将其变为新主节点。如下图,哨兵 leader 向被选中的从节点 server2 发送 SLAVEOF no one 命令,将该从节点升级为新主节点。

在发送 SLAVEOF no one 命令之后,哨兵 leader 会以每秒一次的频率向被升级的从节点发送 INFO 命令(没进行故障转移之前,INFO 命令的频率是每十秒一次),并观察命令回复中的角色信息,当被升级节点的角色信息从原来的 slave 变为 master 时,哨兵 leader 就知道被选中的从节点已经顺利升级为主节点了。
如下图,选中的从节点 server2 升级成了新主节点:

步骤二:将从节点指向新主节点
当新主节点出现之后,哨兵 leader 下一步要做的就是,让已下线主节点属下的所有「从节点」指向「新主节点」,这一动作可以通过向「从节点」发送 SLAVEOF 命令来实现。
如下图,哨兵 leader 向所有从节点(server3和server4)发送 SLAVEOF,让它们成为新主节点的从节点。

步骤三:通知客户的主节点已更换
经过前面一系列的操作后,哨兵集群终于完成主从切换的工作,那么新主节点的信息要如何通知给客户端呢?
这主要通过 Redis 的发布者/订阅者机制来实现的。每个哨兵节点提供发布者/订阅者机制,客户端可以从哨兵订阅消息。哨兵提供的消息订阅频道有很多,不同频道包含了主从节点切换过程中的不同关键事件,几个常见的事件如下:

客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了。
通过发布者/订阅者机制机制,有了这些事件通知,客户端不仅可以在主从切换后得到新主节点的连接信息,还可以监控到主从节点切换过程中发生的各个重要事件。这样,客户端就可以知道主从切换进行到哪一步了,有助于了解切换进度。
步骤四:将旧主节点变为从节点
故障转移操作最后要做的是,继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点,如下图:

至此,整个主从节点的故障转移的工作结束。
6.哨兵集群是如何组成的
事实上,在我们配置哨兵的信息时,只需要填下面这几个参数:设置主节点名字、主节点的 IP 地址和端口号以及 quorum 值。
sentinel monitor <master-name> <ip> <redis-port> <quorum>
不需要填其他哨兵节点的信息,它们是如何感知对方的呢?
实际上也是通过Redis 的发布者/订阅者机制来相互发现的。在主从集群中,主节点上有一个名为__sentinel__:hello 的频道,不同哨兵就是通过它来相互发现,实现互相通信的。
在下图中,哨兵 A 把自己的 IP 地址和端口的信息发布到__sentinel__:hello 频道上,哨兵 B 和 C 订阅了该频道。那么此时,哨兵 B 和 C 就可以从这个频道直接获取哨兵 A 的 IP 地址和端口号。然后,哨兵 B、C 可以和哨兵 A 建立网络连接。

通过这个方式,哨兵 B 和 C 也可以建立网络连接,这样一来,哨兵集群就形成了。
7.哨兵集群如何知道从节点的信息
主节点知道所有「从节点」的信息,所以哨兵会每 10 秒一次的频率向主节点发送 INFO 命令来获取所有「从节点」的信息。
如下图所示,哨兵 B 给主节点发送 INFO 命令,主节点接受到这个命令后,就会把从节点列表返回给哨兵。接着,哨兵就可以根据从节点列表中的连接信息,和每个从节点建立连接,并在这个连接上持续地对从节点进行监控。哨兵 A 和 C 可以通过相同的方法和从节点建立连接。

通过 Redis 的发布者/订阅者机制,哨兵之间可以相互感知,然后组成集群,同时,哨兵又通过 INFO 命令,在主节点里获得了所有从节点连接信息,于是就能和从节点建立连接,并进行监控了。
三.集群分片
1.问题引入
哨兵模式解决了高可用、高并发读的问题,但仍然有缺点:
- 哨兵模式下每台 Redis 服务器都存储相同的数据,很浪费内存,难以存储海量数据
- 哨兵模式下只有一个Master,难以支持高并发写
于是引出了集群模式
2.集群模式的特征
- Redis集群支持多个Master,每个Master又可以挂载多个slave,并且每一个Master保存不同的数据
- Master之间通过ping互相检测彼此的健康状态,相互选举,不再依赖sentinel
- 客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可,因为最终都会被转发到正确节点上
注:集群节点保存键值对以及键值对过期时间的处理方式与Redis单机模式是一样的,唯一不同就是节点只能使用0号数据库,而单机Redis服务器则没有限制。
3.数据分区理论
①节点取余分区
用户每次读写操作都是根据公式:hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
这种方案存在一个问题:当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移,缓存将全部失效
②一致性Hash分区
这里我就不展开讲了,推荐去看好刚老师的视频,讲的贼好
好刚: 7分钟视频详解一致性hash 算法_哔哩哔哩_bilibili
③哈希槽分区
Redis集群没有使用一致性hash,而是引入了哈希槽的概念。
哈希槽(slot)位于数据和节点之间,用于管理数据和节点之间的关系,就相当于节点上放的是槽,槽里放的是数据。

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
Redis集群有16384个哈希槽,先计算每个key的CRC16哈希值,然后对16384取模来决定放置在哪个槽。集群的每个节点负责一部分的hash槽。比如当前集群如果有3个节点,那么:

4.为什么Redis集群的最大槽数是16384
CRC16算法产生的hash值有16bit,该算法可以产生2^16=65536个值。 换句话说值是分布在0~65535之间,有更大的65536不用为什么只用16384就够?作者在做mod运算的时候,为什么不mod65536,而选择mod16384?
Redis集群的节点会按照以下规则发ping消息:
- 每秒会随机选取5个节点,找出最久没有通信的节点发送ping消息
- 每100毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster-node-timeout/2 则立刻发送ping消息
心跳包的消息头里面有个myslots的char数组,是一个bitmap,每一个位(bit)代表一个槽,如果该位为1,表示这个槽是属于这个节点的。

(1) 如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大,浪费带宽
- 在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb
- 在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb
(2) redis的集群主节点数量基本不可能超过1000个,太多可能造成网络拥堵
集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
(3) 槽位越小,压缩比高,容易传输
Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,bitmap的填充率 (slots / N,N表示节点数) 越低,压缩率越高。所以插槽数越低,填充率会降低,压缩率会提高
5.在集群中执行命令
对数据库中的16384个槽都进行了指派后,集群就会进入上线状态,这是客户端就可以向集群中的节点发送数据命令,需要进行计算出命令要处理的键是属于哪个槽的,并检查是否指派给了自己。
如果键所在的槽正好指派当前节点,那么节点直接执行这个命令:

如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误(集群模式下,MOVED错误是回被隐藏的,不会显示的,而是直接显示Redirected),指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。


6.重新分派槽位
当Redis集群进行扩容/缩容时,会重新分派槽位。
- Redis集群重新分派槽位操作可以将任意数量的已经指派给某个节点的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点上。
- 重新分片操作可以在线进行,在重新分片过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
- Redis集群的重新分派槽位操作是由集群管理软件redis-trib负责执行的,Redis提供了进行重新分片所需要的所有命令,redis-trib则通过向源节点和目标节点发送命令来重新分片操作。

7. MOVED错误与ASK错误
MOVED错误
之前讲过,如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误(会被隐藏)。
MOVED错误的格式为:
MOVED <slot> <ip>:<port>
其中slot为键所在的槽,而ip和port则是负责处理槽slot的节点的IP地址和端口号
# 表示槽10086正由127.0.0.1,端口号为7002的节点负责。
MOVED 10086 127.0.0.1:7002
一个集群客户端通常会与集群中的多个节点创建套接字(Socket)连接,而所谓的节点转向实际上就是换一个套接字(Socket)来发送命令。
如果客户端尚未与想要转向的节点创建套接字(Socket)连接,那么客户端会先根据MOVED错误提供的IP地址和端口号来连接节点,然后再进行转向。
ASK错误
在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。
当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:
- 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
- 相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误(也会被隐藏),指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。

和接到MOVED错误时的情况类似,集群模式的redis-cli在接到ASK错误时也不会打印错误,而是自动根据错误提供的IP地址和端口进行转向动作。
ASK 错误与MOVED 错误的区别
- MOVED错误表示槽的负责权已经从一个节点转移到另外的节点。
- ASK错误则是表示两个节点在迁移槽过程中对key处理的负责权。
8. Redis Cluser是如何保证高可用的
Redis Cluster保证高可用主要还是依靠:**故障检测与故障转移两种策略。**实际上这两种策略的处理过程与哨兵模式的处理类似。
故障检测
集群中每个节点都会定期地向集群中的其他节点发送PING消息,以此检测对方是否在线;如果接收PING消息的节点没有在规定的时间内,向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将PING消息节点标记为疑似下线(possible fail,PFAIL)。

如果在集群中,超过半数以上负责处理槽的主节点都将某个节点X标记为PFAIL,则某个主节点就会将这个主节点X就会被标记为已下线(FAIL),并且广播到这条消息,这样其他所有的节点都会立即将主节点X标记为FAIL。
假设:
- Redis Cluster有四个主节点:7000-7003,两个从节点:7004与7005
- 此时7000已下线,并且主节点7001认为主节点7000进入PFAIL
- 同时主节点7002、7003也认为主节点7000进入下线状态
这样一来超过半数的主节点都认为7000节点FAIL,那么7001便会标记7000为FAIL状态,并向集群广播主节点7000已经FAIL消息。

故障转移
步骤一:选出新主节点
- 在该下线主节点的所有从节点中,选择一个做主节点。这里的选举方法也与哨兵类似。
- 被选中的从节点会执行 SLAVEOF no one 命令,成为新的主节点;
步骤二:将已下线主节点的槽派给新节点
新的主节点会撤销对所有对已下线主节点的槽指派,并将这些槽全部派给自己。
步骤三:通知集群主节点已更换
新的主节点向集群广播一条PONG消息,让其他节点知道"我已经变成主节点了,并且我会接管已下线节点负责的处理的槽";
新主节点开始接收和自己负责处理的槽有关的命令请求
步骤四:将旧主节点变为从节点
举例说明:包含7000、7001、7002、7003四个主节点的集群,我们此时加入7004、7005两个节点,并当做7000的主节点的两个从节点。

如果此时主节点7000下线(宕机),那么集群中仍然有几个主节点将在节点7000的两个从节点7004、7005中选择一个节点作为主节点,比如选择了7004则这个新节点将接管原来节点7000负责处理的槽,并继续处理客户端发送的请求,而7005此时作为7004的从节点。

如果下线的7000节点,又重新上线的话,那它将作为节点7004的从节点。
