Redis高可用之主从复制、哨兵、cluster集群

116 阅读27分钟

Redis 高可用

1.1 什么是高可用

在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。

高可用的计算公式是1-(宕机时间)/(宕机时间+运行时间)有点类似与网络传输的参数误码率,我们用9的个数表示可用性:

2个9:99%,一年内宕机时长:1%×365天=3.6524天=87.6h

4个9:99.99%,一年内宕机时长:0.01%×365天=52.56min

5个9:99.999%,一年内宕机时长:0.001%*365天=5.265min

11个9:几乎一年宕机时间只有几秒钟

但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。

1.2 Redis的高可用技术

在Redis中,实现高可用的技术主要包括持久化、主从复制、哨兵和cluster集群,下面分别说明它们的作用,以及解决了什么样的问题。

  • 持久化: 持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。

  • 主从复制: 主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份(和同步),以及对于读操作的负载均衡和简单的故障恢复。

    • 缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
  • 哨兵: 在主从复制的基础上,哨兵实现了自动化的故障恢复。(主挂了,找一个从成为新的主,哨兵节点进行监控)

    • 缺陷:写操作无法负载均衡;存储能力受到单机的限制。
  • Cluster集群: 通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。(6台起步,成双成对,3主3从)

持久化技术已在前一篇文章中进行介绍( Redis 高可用之持久化 - 掘金 (juejin.cn) ),本文将具体主从复制、哨兵、Cluster集群三种高可用技术。

二、Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

Redis主从复制的配置十分简单,它可以使从服务器是主服务器的完全拷贝。需要清除Redis主从复制的几点重要内容

  • Redis使用异步复制。但从Redis 2.8开始,从服务器会周期性的应答从复制流中处理的数据量。
  • 一个主服务器可以有多个从服务器。
  • 从服务器也可以接受其他从服务器的连接。除了多个从服务器连接到一个主服务器之外,多个从服务器也可以连接到一个从服务器上,形成一个图状结构。
  • Redis主从复制不阻塞主服务器端。也就是说当若干个从服务器在进行初始同步时,主服务器仍然可以处理请求。
  • 主从复制也不阻塞从服务器端。当从服务器进行初始同步时,它使用旧版本的数据来应对查询请求,假设你在redis.conf配置文件是这么配置的。否则的话,你可以配置当复制流关闭时让从服务器给客户端返回一个错误。但是,当初始同步完成后,需要删除旧的数据集和加载新的数据集,在这个短暂的时间内,从服务器会阻塞连接进来的请求。
  • 主从复制可以用来增强扩展性,使用多个从服务器来处理只读的请求(比如,繁重的排序操作可以放到从服务器去做),也可以简单的用来做数据冗余。
  • 使用主从复制可以为主服务器免除把数据写入磁盘的消耗:在主服务器的redis.conf文件中配置“避免保存”(注释掉所有“保存“命令),然后连接一个配置为“进行保存”的从服务器即可。但是这个配置要确保主服务器不会自动重启。

1.1 主从复制的作用

  • 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  • 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

主从复制2.webp

Redis主从架构可以解决单机的读写瓶颈问题,但是没有自动故障转移功能,不能解决master单点故障问题。

1.2 主从复制流程

Snipaste_2022-11-08_19-11-17.png

(1)若启动一个slave机器进程,则它会向Master机器发送一个sync command命令,请求同步连接。

(2)无论是第一次连接还是重新连接,Master机器都会启动一个后台进程,将数据快照保存到数据文件中(执行rdb操作),同时Master还会记录修改数据的所有命令并缓存在数据文件中.

(3)后台进程完成缓存操作之后,Master机器就会向slave机器发送数据文件,slave端机器将数据文件保存到硬盘上,然后将其加载到内存中,接着Master机器就会将修改数据的所有操作一并发送给slave端机器。若slave出现故障导致宕机,则恢复正常后会自动重新连接。

(4)Master机器收到slave端机器的连接后,将其完整的数据文件发送给slave端机器,如果Mater同时收到多个slave发来的同步请求,则Master会在后台启动一个进程以保存数据文件,然后将其发送给所有的slave端机器,确保所有的slave端机器都正常。

三、搭建Redis主从复制

实验环境:

主从虚机IP地址
mastercentos7-1192.168.132.6
slave1centos7-2192.168.132.7
slave2centos7-3192.168.132.8

实验部署

所有节点安装Redis

#关闭防火墙
systemctl stop firewalld
setenforce 0
#安装环境依赖包,下载编译工具
yum install -y gcc gcc-c++ make
​
#上传软件包并解压
cd /opt/
tar zxvf redis-5.0.7.tar.gz -C /opt/
cd /opt/redis-5.0.7/
#开2核编译安装,指定安装路径为/usr/local/redis
make -j2 && make PREFIX=/usr/local/redis install
#由于Redis源码包中直接提供了Makefile 文件,所以在解压完软件包后,不用先执行./configure 进行配置,可直接执行make与make install命令进行安装。#执行软件包提供的install_server.sh 脚本文件,设置Redis服务所需要的相关配置文件
cd /opt/redis-5.0.7/utils
./install_server.sh
.......#一直回车
Please select the redis executable path [] /usr/local/redis/bin/redis-server
#这里默认为/usr/local/bin/redis-server,需要手动修改为/usr/local/redis/bin/redis-server,注意要一次性正确输入
​
 ---------------------- 虚线内是注释 ----------------------------------------------------
 Selected config:
 Port: 6379                                      #默认侦听端口为6379
 Config file: /etc/redis/6379.conf               #配置文件路径
 Log file: /var/log/redis_6379.log               #日志文件路径
 Data dir : /var/lib/redis/6379                  #数据文件路径
 Executable: /usr/local/redis/bin/redis-server   #可执行文件路径
 Cli Executable : /usr/local/bin/redis-cli       #客户端命令工具
 -----------------------------------------------------------------------------------
​
#当install_server.sh 脚本运行完毕,Redis 服务就已经启动,默认监听端口为6379
netstat -natp | grep redis
​
#把redis的可执行程序文件放入路径环境变量的目录中,便于系统识别
ln -s /usr/local/redis/bin/* /usr/local/bin/
​
#Redis服务控制
/etc/init.d/redis_6379 stop     #停止
/etc/init.d/redis_6379 start    #启动
/etc/init.d/redis_6379 restart  #重启
/etc/init.d/redis_6379 status   #查看状态

1.png

2.png

修改master节点的配置文件

vim /etc/redis/6379.conf 
 bind 0.0.0.0                      #70行,修改监听地址为0.0.0.0(生产环境中需要填写物理网卡的IP)
 daemonize yes                     #137行,开启守护进程,后台启动 
 logfile /var/log/redis_6379.log   #172行,指定日志文件存放目录
 dir /var/lib/redis/6379           #264行,指定工作目录
 appendonly yes                    #700行,开启AOF持久化功能
​
/etc/init.d/redis_6379 restart     #重启redis服务

修改slave节点的配置文件

修改slave1的配置文件,之后scp传给slave2。

#修改slave1的配置文件
vim /etc/redis/6379.conf 
 bind 0.0.0.0                        #70行,修改监听地址为0.0.0.0(生产环境中需要填写物理网卡的IP)
 daemonize yes                       #137行,开启守护进程,后台启动
 logfile /var/log/redis_6379.log     #172行,指定日志文件目录
 dir /var/lib/redis/6379             #264行,指定工作目录
 replicaof 192.168.132.6 6379       #288行,指定要同步的Master节点的IP和端口
 appendonly yes                      #700行,修改为yes,开启AOF持久化功能#将配置文件传给slave2
scp /etc/redis/6379.conf 192.168.132.8:/etc/redis/
​
/etc/init.d/redis_6379 restart  #重启redis
netstat -natp | grep redis      #查看主从服务器是否已建立连接

3.png

验证主从效果

1)主节点查看日志,并插入一条数据。

 #主节点查看日志
[root@mas utils]# tail /var/log/redis_6379.log 

#主节点插入一条数据
[root@mas utils]# redis-cli
127.0.0.1:6379> set class student
OK
127.0.0.1:6379> keys *
1) "class"
127.0.0.1:6379> get class
"student"

4.png

5.png

2)从节点查看数据是否同步成功。

[root@slave1 utils]# redis-cli     #slave1查看数据同步成功  
127.0.0.1:6379> keys *
1) "class"
127.0.0.1:6379> get class
"student"
​
[root@slave2 utils]# redis-cli     #slave2查看数据同步成功  
127.0.0.1:6379> keys *
1) "class"
127.0.0.1:6379> get class
"student"

6.png

四、Redis哨兵模式

主从切换技术的方法是:当服务器宕机后,需要手动一台从机切换为主机,这需要人工干预,不仅费时费力而且还会造成一段时间内服务不可用。为了解决主从复制的缺点,就有了哨兵机制。

哨兵的核心功能:在主从复制的基础上,哨兵引入了主节点的自动故障转移。

Snipaste_2022-11-08_19-17-16.png

上图所示,多个哨兵之间也存在互相监控,这就形成了多哨兵模式,现在对该模式的工作过程进行讲解,介绍如下:

主观下线

主观下线,适用于主服务器和从服务器。如果在规定的时间内(配置参数:down-after-milliseconds),Sentinel 节点没有收到目标服务器的有效回复,则判定该服务器为“主观下线”。比如 Sentinel1 向主服务发送了PING命令,在规定时间内没收到主服务器PONG回复,则 Sentinel1 判定主服务器为“主观下线”。

客观下线

客观下线,只适用于主服务器。 Sentinel1 发现主服务器出现了故障,它会通过相应的命令,询问其它 Sentinel 节点对主服务器的状态判断。如果超过半数以上的  Sentinel 节点认为主服务器 down 掉,则 Sentinel1 节点判定主服务为“客观下线”。

投票选举

投票选举,所有 Sentinel 节点会通过投票机制,按照谁发现谁去处理的原则,选举 Sentinel1 为领头节点去做 Failover(故障转移)操作。Sentinel1 节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。

对上对述过程做简单总结:

Sentinel 负责监控主从节点的“健康”状态。当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接 Redis 集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。

2.1 哨兵模式的作用

  • 监控: 哨兵会不断地检查主节点和从节点是否运作正常。
  • 自动故障转移: 当主节点不能正常工作时,哨兵会开始自动故障转移操,它会将失效主节点的其中一个从节点升级为新的主节点,并让其它从节点改为复制新的主节点。
  • 通知(提醒): 哨兵可以将故障转移的结果发送给客户端。

哨兵2.png

2.2 哨兵结构

哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据

数据节点: 主节点和从节点都是数据节点。

2.3 故障转移机制

1、由哨兵节点定期监控发现主节点是否出现了故障

每个哨兵节点每隔1秒会问主节点、从节点及其它哨兵节点发送一次ping命令做一次心检测。如果主节点在一定时间范围内不回复或者是回复一个错误消息,那么这个哨兵就会认为这个主节点主观下线了(单方面的)。当超过半数哨兵节点认为该主节点主观下线了,这样就客观下线了。

2、当主节点出现故障,此时哨兵节点会通过Raft算法(选举算法)实现选举机制共同选举出一个哨兵节点为leader,来负责处理主节点的故障转移和通知。所以整个运行哨兵的集群的数量不得少于3个节点。

3、由leader哨兵节点执行故障转移,过程如下:

  • 将某一个从节点升级为新的主节点,让其它从节点指向新的主节点;
  • 若原主节点恢复也变成从节点,并指向新的主节点;
  • 通知客户端主节点已经更换。

需要特别注意的是,客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作

2.4 主节点的选举

1.过滤掉不健康的(己下线的),没有回复哨兵ping响应的从节点。

2.选择配置文件中从节点优先级配置最高的。(replica-priority,默认值为100)

3.选择复制偏移量最大,也就是复制最完整的从节点。

哨兵的启动依赖于主从模式,所以须把主从模式安装好的情况下再去做哨兵模式。

五、搭建Redis哨兵模式

Redis哨兵模式
1.核心功能

在主从复制的基础上,哨兵引入了主节点的自动故障转移 哨兵模式原理
哨兵(sentinel)∶是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的Master并将所有 Slave 连接到新的Master。所以整个运行哨兵的集群的数量不得少于3个节点。

  1. 哨兵模式的作用
    监控∶哨兵会不断地检查主节点和从节点是否运作正常。 自动故障转移∶当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。

**通知(提醒)**∶哨兵可以将故障转移的结果发送给客户端。

3.哨兵模式的组成

哨兵节点∶哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据,端口是26379
数据节点∶主节点和从节点都是数据节点

4.哨兵的工作模式

所有哨兵都会监控节点,哨兵之间会共享服务器的状态数据,对整个集群实现监控
哨兵的启动依赖于主从模式,所以须把主从模式安装好的情况下再去做哨兵模式,所有节点上都需要部署哨兵模式,哨兵模式会监控所有的 Redis 工作节点是否正常,当Master出现问题的时候,因为其他节点与主节点失去联系,因此会投票,投票过半就认为这个Master的确出现问题,然后会通知哨兵间,然后从Slaves中选取一个作为新的Master
需要特别注意的是,客观下线是主节点才有的概念,如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作
1)主观下线:当某个哨兵认为节点宕机,是主观下线
2)客观下线:当所有哨兵投票后票数过半后确认宕机为客观下线,然后就会执行故障的切换等过程;

5.哨兵模式的搭建

主机操作系统ip所需软件
master服务器centos7192.168.132.6redis-5.0.7
slave服务器centos7192.168.132.7redis-5.0.7
slave2服务器centos7192.168.132.8redis-5.0.7

5.1 修改 Redis 配置文件(所有节点操作)
Master:192.168.132.6
Slave1:192.168.132.7
Slave2:192.168.132.8

vim /opt/redis-5.0.7/sentinel.conf
#17行,关闭保护模式
protected-mode no
#21行,Redis哨兵默认的监听端口
port 26379
#26行,指定sentinel为后台启动
daemonize yes
#36行,指定日志存放路径
logfile "/var/log/sentinel.log"
#65行,指定数据库存放路径
dir "/var/lib/redis/6379"
#84行,修改 指定该哨兵节点监控192.168.6.170:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移
sentinel monitor mymaster 192.168.132.6 6379 2
#113行,判定服务器down掉的时间周期,默认30000毫秒(30秒)
sentinel down-after-milliseconds mymaster 30000
#146行,故障节点的最大超时时间为180000(180秒)
sentinel failover-timeout mymaster 180000


6.先启master,再启slave

cd /opt/redis-5.0.7/
redis-sentinel sentinel.conf &
  1. 验证结果
    master:192.168.132.6
    redis-cli -p 26379 info sentinel

[root@www redis-5.0.7]# redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3
[2]+  完成                  redis-sentinel sentinel.conf

7.png

8.png

9.png

六、Redis 集群模式

集群,即Redis Cluster,是Redis3.0开始引入的分布式存储方案。

集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。

6.1 集群的作用

(1)数据分区: 数据分区(或称数据分片)是集群最核心的功能。

  • 集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。
  • Redis单机内存大小受限问题,在介绍持久化和主从复制时都有提及;例如,如果单机内存太大,bgsave和bgrewriteaof的fork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出。

(2)高可用: 集群支持主从复制和主节点的自动故障转移(与哨兵类似);当任一节点发生故障时,集群仍然可以对外提供服务。

通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

6.2 Redis集群的数据分片

Redis集群引入了哈希槽的概念。

Redis集群有16384个哈希槽(编号0-16383)。

集群的每个节点负责一部分哈希槽。

每个Key通过CRC16校验后对16384取余来决定放置哪个哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

以3个节点组成的集群为例:

  • 节点A包含0到5460号哈希槽
  • 节点B包含5461到10922号哈希槽
  • 节点c包含10923到16383号哈希槽

集群2.webp

6.3 集群模式的主从复制模型

  • 集群中具有A、B、C三个节点,如果节点B失败了,整个集群就会因缺少5461-10922这个范围的槽而不可以用。
  • 为每个节点添加一个从节点A1、B1、C1整个集群便有三个Master节点和三个slave节点组成,在节点B失败后,集群选举B1位为主节点继续服务。当B和B1都失败后,集群将不可用。

七、搭建 Redis 集群

环境配置/安装包

安装包:redis-5.0.7.tar.gz

主机操作系统IP地址软件 / 安装包 / 工具
软件 / 安装包 / 工具192.168.6.156redis-5.0.7.tar.gz

创建每个节点的工作目录

cd /etc/redis/
mkdir -p redis-cluster/redis600{1..6}   #创建redis集群的工作目录、和每个节点的工作目录
ls -R redis-cluster/

10.png

到软件包目录,把redis配置文件分别复制到我们第二步所创建的每个节点目录中。也要把/opt/redis-5.0.7/src下的客户端工具redis-cli和服务端命令redis-server复制到每个节点的目录中

cd /opt/redis-5.0.7/src/
ls
cd /opt/redis-5.0.7/
for i in {1..6}; do cp /opt/redis-5.0.7/redis.conf /etc/redis/redis-cluster/redis600$i; cp /opt/redis-5.0.7/src/redis-server /opt/redis-5.0.7/src/redis-cli /etc/redis/redis-cluster/redis600$i; done      
#使用for循环复制可以节省很多时间
cd /etc/redis/redis-cluster/
ls -R
 

11.png

修改每个节点目录中的redis配置文件

cd redis6001
vim redis.conf
 
#bind 127.0.0.1     ##第69行;注释掉监听地址,表示监听任意地址(也可设置为0.0.0.0)
protected-mode no    #修改第88行,关闭保护模式,设置为no
port 6001           #修改第92行,修改监听端口为6001
daemonize yes       #修改第136行,开启后台运行
appendonly yes      #修改第699行,开启AOF持久化
 
 
#集群模式配置
cluster-enabled yes    #第832行取消注释,开启集群模式
cluster-config-file nodes-6001.conf   #第840行,每个集群配置文件,取消注释、修改对应的端口(每个节点都要设置对应的)
cluster-node-timeout 15000   #第846行,集群节点之间通信的超时时间;取消注释

启动redis服务,一定要先进入每个节点目录中去使用redis-server命令启动


cd ../redis6001
ls
for i in {1..6}; do cd /etc/redis/redis-cluster/redis600$i; ./redis-server redis.conf; done
ps -elf |grep redis
redis-cli --cluster create 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003 127.0.0.1:6004 127.0.0.1:6005 127.0.0.1:6006 --cluster-replicas 1                                 #把节点加入到集群当中
--cluster-replicas    指定每个主节点有多少个从节点做主从复制
 
 
redis-cli -p 6001    #登录redis数据库
cluster slots        #查看主从对应关系

12.png

13.png

14.png

验证,键的插入

redis-cli -p 6001 -c    
-c  实现节点间的跳转,如果不加会报错
set name hj

15.png

 登录到6004节点

redis-cli -p 6004 -c
keys *
get name

16.png 登录到6006节点上

17.png 查看集群模式节点状态

cluster nodes

Redis集群拓展:

数据量过多如何处理?

当数据量过多的情况下,一种简单的方式是升级Redis实例的资源配置,包括增加内存容量、磁盘容量、更好配置的CPU等,但这种情况下Redis使用RDB进行持久化的时候响应会变慢,Redis通过fork子进程来完成数据持久化,但fork在执行时会阻塞主线程,数据量越大,fork的阻塞时间就越长,从而导致Redis响应变慢。

Redis的切片集群可以解决这个问题,也就是启动多个Redis实例来组成一个集群,再按照一定的规则把数据划分为多份,每一份用一个实例来保存,这样客户端只需要访问对应的实例就可以获取数据。在这种情况下fork子进程一般不会给主线程带来较长时间的阻塞,如下图:

图片

切片集群架构图
将20GB的数据分为4分,每份包含5GB数据,客户端只需要找到对应的实例就可以获取数据,从而减少主线程阻塞的时间。

当数据量过多的时候,可以通过升级Redis实例的资源配置或者通过切片集群的方式。前者实现起来简单粗暴,但这数据量增加的时候,需要的内存也在不断增加,主线程fork子进程就有可能会阻塞,而且该方案受到硬件和成本的限制。相比之下第二种方案是一种扩展性更好的方案,如果想保存更多的数据,仅需要增加Redis实例的个数,不用担心单个实例的硬件和成本限制。在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。

选择切片集群也是需要解决一些问题的:

  • 数据切片后,在多个实例之间怎么分布?
  • 客户端怎么确定想要访问的实例是哪一个?

Redis采用了Redis Cluster的方案来实现切片集群,具体的Redis Cluster采用了哈希槽(Hash Slot)来处理数据和实例之间的映射关系。在Redis Cluster中,一个切片集群共有16384个哈希槽(为什么Hash Slot的个数是16384),这些哈希槽类似于数据的分区,每个键值对都会根据自己的key被影射到一个哈希槽中,映射步骤如下:

  • 根据键值对key,按照CRC16算法计算一个16bit的值。
  • 用计算的值对16384取模,得到0~16383范围内的模数,每个模数对应一个哈希槽。

这时候可以得到一个key对应的哈希槽了,哈希槽又是如何找到对应的实例的呢?

在部署Redis Cluster的时候,可以通过cluster create命令创建集群,此时Redis会自动把这些槽分布在集群实例上,例如一共有N个实例,那么每个实例包含的槽个数就为16384/N。当然可能存在Redis实例中内存大小配置不一的问题,内存大的实例具有更大的容量。这种情况下可以通过cluster addslots命令手动分配哈希槽。

redis-cli -h 33.33.33.3p 6379 cluster addslots 0,1redis-cli -h 33.33.33.4p 6379 cluster addslots 2,3redis-cli -h 33.33.33.5p 6379 cluster addslots 4
复制代码

要注意的是,如果采用cluster addslots的方式手动分配哈希槽,需要将16384个槽全部分配完,否则Redis集群无法正常工作。现在通过哈希槽,切片集群就实现了数据到哈希槽、哈希槽到实例的对应关系,那么客户端如何确定需要访问的实例是哪一个呢?

(二)客户端定位集群中的数据

客户端请求的key可以通过CRC16算法计算得到,但客户端还需要知道哈希槽分布在哪个实例上。在最开始客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端,实例之间会把自己的哈希槽信息发给和它相连的实例,完成哈希槽的扩散。这样客户端访问任何一个实例的时候,都能获取所有的哈希槽信息。当客户端收到哈希槽的信息后会把哈希槽对应的信息缓存在本地,当客户端发送请求的时候,会先找到key对应的哈希槽,然后就可以给对应的实例发送请求了。

但是,哈希槽和实例的对应关系不是一成不变的,可能会存在新增或者删除的情况,这时候就需要重新分配哈希槽;也可能为了负载均衡,Redis需要把所有的实例重新分布。

虽然实例之间可以互相传递消息以获取最新的哈希槽分配信息,但是客户端无法感知这个变化,就会导致客户端访问的实例可能不是自己所需要的了。

Redis Cluster提供了重定向的机制,当客户端给实例发送数据读写操作的时候,如果这个实例上没有找到对应的数据,此时这个实例就会给客户端返回MOVED命令的相应结果,这个结果中包含了新实例的访问地址,此时客户端需要再给新实例发送操作命令以进行读写操作,MOVED命令如下:

GET hello:key(error) MOVED  33.33.33.33:6379
复制代码

返回的信息代表客户端请求的key所在的哈希槽为3333,实际是在33.33.33.33这个实例上,此时客户端只需要向33.33.33.33这个实例发送请求就可以了。

此时也存在一个小问题,哈希槽中对应的数据过多,导致还没有迁移到其他实例,此时客户端就发起了请求,在这种情况下,客户端就对实例发起了请求,如果数据还在对应的实例中,会给客户端返回数据;如果请求的数据已经被转移到其他实例上,客户端就会收到实例返回的ASK命令,该命令表示:哈希槽中数据还在前一种、ASK命令把客户端需要访问的新实例返回了。此时客户端需要给新实例发送ASKING命令以进行请求操作。

值得注意的是ASK信息和MOVED信息不一样,ASK信息并不会更新客户端本地的缓存的哈希槽分配信息,也就是说如果客户端再次访问该哈希槽还是会请求之前的实例,直到数据迁移完成。