Redis集群

发布于 2023-08-17  227 次阅读


目标:什么是Redis集群、Redis集群原理、怎么搭建Redis集群

集群介绍

集群(cluster)就是一组计算机,它们作为一个整体向用户提供一组网络资源,这些单个的计算机系统就是集群的节点(Node)。集群提供了一下关键的特性。

  • 可扩展性。集群的性能不能限于单一的服务实体,新的服务实体可以动态的加入到集群,从而增强集群的性能。
  • 高可用性。集群通过服务实体冗余使客户端免于轻易找遇到“out of service”警告。当一台节点服务器发生故障的时候,这台服务器上所运行的应用程序将在另一节点服务器上被自动接管。消除单点故障对于增强数据可用性、可达性和可靠性是非常重要的。
  • 负载均衡。负载均衡能把任务比较均匀的分布到集群环境下的计算和网络资源,以便提高数据吞吐量。
  • 错误回复。如果集群中的某一台服务器由于故障或者维护需要而无法使用,资源和应用程序将转移到可用的集群节点上。这种由于某个节点中的资源不能工作,另一个可用节点中的资源能够透明的接管并且接续完成任务的过程叫做错误恢复。

分布式与集群的联系与区别如下:

  • 分布式是指将不同的业务分布在不同的地方。
  • 而集群指的是将几台服务器集中在一起,实现同一业务。
  • 分布式的每一个节点,都可以做集群,而集群并不一定就是分布式。而分布式,从狭义上理解,也与集群差不多,但是它的组织比较松散,不像集群,有一定组织性,一台服务器宕机了,其他的服务器可以顶上来。分布式的每一个节点,都完成不同的业务,一个节点宕了,这个业务就不可访问了。

集群的主要分成三大类:

  • HA:高可用集群(High Availability Cluster)
  • LBC:负载均衡集群/负载均衡系统(Load Balance Cluster)
  • HPC:科学计算集群(High Performance Computing Cluster)/高性能计算(High Performance Computing)集群。

Redis集群架构

假设上千万、上亿用户同时访问Redis,QPS(请求数/秒)达到了10万+。这些请求过来,单机Redis直接就挂了。系统的瓶颈就出现在Redis单机问题上,此时我们可以通过主从复制解决该问题,实现系统的高并发。

主从模式中,当主节点宕机之后,从节点是可以作为主节点顶上来继续提供服务,但是需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点。整个过程需要人工干预。于是,再Redis2.8版本开始,引入了哨兵(Sentinel)这个概念,在主从复制的基础上,哨兵实现了自动化故障恢复。

哨兵模式中,单个节点的写能力,存储能力收到了单机的限制,动态扩容困难复杂。于是Redis3.0版本正式推出了Redis Cluster 集群模式,有效的解决了Redis分布式方面的需求。Redis Cluster 集群模式具有高可用、可扩展性、分布式、容错等特性。

Redis Cluster 采用无中心结构,每个节点都可以保存数据和整个集群状态,每个节点都和其他的所有节点连接。Cluster一般由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群,其中三个为主节点,三个为从节点。三个主节点会分配槽,处理客户端的命令请求,而从节点可以用在主节点故障后,顶替主节点。

配置一个Redis Cluster我们需要准备至少6台Redis,为什么至少6台呢?我们可以在Redis官网文档中找到这样一句话:

Note that the minimal cluster that works as expected requires to contain at least three master nodes. 

因为最小的Redis集群,需要至少3个主节点,既然有3个主节点,而一个主节点搭配至少一个从节点,因此至少需要6台Redis。如上图所示,该集群中包含了6个Redis节点,3主3从,分别为M1、M2、M3、S1、S2、S3、除了主从Redis节点之间进行数据恢复复制外,所有Redis节点之间采用Gossip协议进行通信,交换维护节点元数据信息。

总结下来就是:读请求分配给Slave节点,写请求分配给Master,数据同步从Master到Slave节点。

主从模式

Redis Cluster 为了保证数据的高可用新,加入了主从模式,一个主节点对应一个或者多个从节点,主节点提供数据存取,从节点复制主节点数据备份,当这个主节点挂掉后,就会通过这个主节点的从节点选取一个来充当主节点,从而保证集群的高可用。

回到刚才那个例子中,集群有A、B、C三个主节点,如果这3个主节点都没有对应的从节点,如果B挂掉了,则集群将无法继续,因为我们不再有办法为5501~11000范围内的哈希槽提供服务。

所以我们在创建集群的时候,一定要为每一个主节点都添加对应的从节点。比如,集群包含主节点A、B、C,以及从节点A1、B1、C1那么即使B怪掉系统也可以继续正确的工作。

因为B1节点属于B节点的子节点,所以Redis集群将会选择B1节点作为新的主节点,集群将会继续正确的提供服务。当B重新开启后,它就会变成B1的从节点。但是请注意,如果节点B和B1同时挂掉,Redis Cluster就无法继续正常的提供服务了。

优点

  • 去中心化
  • 可扩展性,数据按照Slot存储分布子在多个节点,节点间数据共享,节点可动态添加或者删除,可动态调整数据分布
  • 高可用新,部分节点不可用时,集群仍可用。通过正佳Slave做备份副本
  • 实现故障自动迁移,节点之间通过Gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升

缺点

  • 数据通过异步复制,无法保证数据强一致性
  • 集群环境搭建略微复杂

原理

单机、主从、哨兵的模式数据都是存储在一个节点上,其他节点进行数据的复制。而单个节点存储的是存在上限的,集群模式就是把数据进行分片存储,当一个分片数据达到上限的时候,还可以分成多个分片。

哈希槽

Redis集群(Cluster)并没有选用一致性哈希,而是采用了哈希槽(SLOT)的这种概念。主要的原因是一致性哈希算法对于数据分布、节点位置的控制并不是很友好。

首先哈希槽其实是两个概念:

  • 哈希算法。Redis Cluster的Hash算法不是简单的hash(),而是crc16算法,一种校验算法。
  • 槽位的概念,空间分配的规则、其实哈希槽的本质和一致性哈希算法非常相似,不同的点就是对于哈希空间的定义。一致性哈希的空间是一个圆环,节点分布是基于圆环的,无法很好的控制数据分布。而Redis Cluster的槽位空间是自定义分配的,类似于Window盘分区的概念。这种分区是可以自定义大小,自定义位置的。

Redis Cluster包含了16384个哈希槽,每个Key通过计算后都会落在具体的一个槽位上,而这个槽位是属于哪个存储节点的,则由用户自己定义分配。例如机器硬盘小的,可以分配少一点槽位,硬盘大的可以分配多一点。如果节点硬盘都差不多则可以平均分配。所以哈希槽这种概念很好的解决了一致性哈希的弊端。

另外再容错性和扩展性上,表象与一致性哈希一样,都是对受影响的数据进行转移。而哈希槽本质上是对槽位的转移,把故障的节点负责的槽位转移到其他正常的节点上。扩展节点也是一样,把其他的节点上的槽位转移到新的节点上。

但一定要注意的是,对于槽位的转移和分派,Redis集群是不会自动进行的,而是需要人工配置的。所以Redis集群的高可用是依赖于节点的主从复制与主从之间的自动故障转移。

16384个slots(槽位)

Redis Cluster没有单机的那种16个数据库(0-15)的概念,而是分成了15384个slots(槽位),每个节点负责其中一部分槽位,槽位的信息存储于每个节点中;当客户端来连接集群时,它先得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个Key时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。

槽位定位算法

Redis Cluster默认会对key值使用CRC16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位。每一个节点负责维护一部分槽以及槽所映射的键值数据。

槽位计算公式:HASH_SLOT = CRC16(key) mod 16384

键空间被分割为 16384 槽(slot),所有的主节点都负责 16384 个哈希槽中的一部分。当集群处于稳定状态时,集群中没有在执行重配置(reconfiguration)操作,每个哈希槽都只由一个节点进行处理(不过主节点可以有一个或多个从节点,可以在网络断线或节点失效时替换掉主节点)。

Redis Cluster 提供了灵活的节点扩容和缩容方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis Cluster 管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。

简单的理解就是:扩容或缩容以后,槽需要重新分配,数据也需要重新迁移,但是服务不需要下线。

假如,这里有 3 个节点的集群环境如下:

  • 节点 A 哈希槽范围为 0 ~ 5500;
  • 节点 B 哈希槽范围为 5501 ~ 11000;
  • 节点 C 哈希槽范围为 11001 ~ 16383。

此时,我们如果要存储数据,按照 Redis Cluster 哈希槽的算法,假设结果是: CRC16(key) % 16384 = 6782。 那么就会把这个 key 的存储分配到 B 节点。此时连接 A、B、C 任何一个节点获取 key,都会这样计算,最终通过 B 节点获取数据。

假如这时我们新增一个节点 D,Redis Cluster 会从各个节点中拿取一部分 Slot 到 D 上,比如会变成这样:

  • 节点 A 哈希槽范围为 1266 ~ 5500;
  • 节点 B 哈希槽范围为 6827 ~ 11000;
  • 节点 C 哈希槽范围为 12288 ~ 16383;
  • 节点 D 哈希槽范围为 0 ~ 1265,5501 ~ 6826,11001 ~ 12287

这种特性允许在集群中轻松地添加和删除节点。同样的如果我想删除节点 D,只需要将节点 D 的哈希槽移动到其他节点,当节点是空时,便可完全将它从集群中移除。

补充:为什么是 16384 个槽

CRC16算法产生的 hash 值有 16bit,该算法可以产生 2^16-=65536 个值。换句话说,值是分布在 0~65535 之间。那作者在做mod运算的时候,为什么不mod65536,而选择mod16384?

对于这个问题,作者给出了解答。原版回答如下图所示,对应的链接地址为:https://github.com/redis/redis/issues/2576

如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。

两个 Redis 节点之间进行通信的时候,会定期发送 PING/PONG 消息,交换数据信息。交换的数据信息,由消息体和消息头组成。消息头包含了发送消息的节点的具体信息,每一个消息必须拥有完整的消息头;消息体无外乎是一些节点标识啊,IP啊,端口号啊,发送时间啊。

在消息头中,最占空间的是 myslots[CLUSTER_SLOTS/8]。当槽位为 65536 时,这块的大小是:65536 ÷ 8 = 8kb 因为每秒钟,Redis 节点需要发送一定数量的 PING 消息作为心跳包,如果槽位为 65536,这个 PING 消息的消息头太大了,浪费带宽。

Redis的集群主节点数量基本不可能超过1000个。

如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此 Redis 作者,不建议 Redis Cluster 节点数量超过 1000 个。那么,对于节点数在 1000 以内的 Redis Cluster 集群,16384 个槽位够用了。没有必要拓展到 65536个。

槽位越小,节点少的情况下,压缩率高。

Redis 主节点的配置信息中,它所负责的哈希槽是通过一张 bitmap 的形式来保存的,在传输过程中,会对 bitmap 进行压缩,但是如果 bitmap 的填充率 slots / N 很高的话(N表示节点数),bitmap 的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap 的压缩率也会很低。而 16384 ÷ 8 = 2kb。(文件压缩率指的是,文件压缩前后的大小比。)

综上所述,作者决定取 16384 个槽,不多不少,刚刚好!

环境搭建

Redis 集群搭建在5版本以前使用Ruby构建集群,而5版本以后直接使用 redis-cli 命令创建集群了。

编写配置文件

三个节点分别创建 redis-*.conf 并添加以下配置(* 为具体端口为了区分文件)。

注意:修改配置文件中所有 IP 和端口部分内容,可以使用 vi 命令 %s/old/new/g 全局替换。

vi /usr/local/redis/cluster/conf/redis-6371.conf(注意配置文件名称起名规则)

 # 放行访问IP限制
bind 0.0.0.0
# 端口
port 6371
# 后台启动
daemonize yes
# 日志存储目录及日志文件名
logfile "/usr/local/redis/cluster/log/redis-6371.log"
# rdb数据文件名
dbfilename dump-6371.rdb
# aof模式开启和aof数据文件名
appendonly yes
appendfilename "appendonly-6371.aof"
# rdb数据文件和aof数据文件的存储目录
dir /usr/local/redis/cluster/data
# 设置密码
requirepass 123456
# 从节点访问主节点密码(必须与 requirepass 一致)
masterauth 123456
# 是否开启集群模式,默认 no
cluster-enabled yes
# 集群节点信息文件,会保存在 dir 配置对应目录下
cluster-config-file nodes-6371.conf
# 集群节点连接超时时间
cluster-node-timeout 15000
# 集群节点 IP
cluster-announce-ip 192.168.10.101
# 集群节点映射端口
cluster-announce-port 6371
# 集群节点总线端口
cluster-announce-bus-port 16371

每个 Redis 集群节点都需要打开两个 TCP 连接。一个用于为客户端提供服务的正常 Redis TCP 端口,例如 6379。还有一个基于 6379 端口加 10000 的端口,比如 16379。

第二个端口用于集群总线,这是一个使用二进制协议的节点到节点通信通道。节点使用集群总线进行故障检测、配置更新、故障转移授权等等。客户端永远不要尝试与集群总线端口通信,与正常的 Redis 命令端口通信即可,但是请确保防火墙中的这两个端口都已经打开,否则 Redis 集群节点将无法通信。

创建 Redis Cluster 集群

分别启动6个节点

 # 分别启动
/usr/local/redis/bin/redis-server /usr/local/redis/cluster/conf/redis-6371.conf
/usr/local/redis/bin/redis-server /usr/local/redis/cluster/conf/redis-6372.conf
/usr/local/redis/bin/redis-server /usr/local/redis/cluster/conf/redis-6373.conf
/usr/local/redis/bin/redis-server /usr/local/redis/cluster/conf/redis-6374.conf
/usr/local/redis/bin/redis-server /usr/local/redis/cluster/conf/redis-6375.conf
/usr/local/redis/bin/redis-server /usr/local/redis/cluster/conf/redis-6376.conf

随便一个 Redis 节点中使用客户端运行以下命令即可(注意IP和端口):

 # 创建集群
/usr/local/redis/bin/redis-cli -a 123456 --cluster create \
192.168.10.101:6371 192.168.10.101:6372 \
192.168.10.102:6373 192.168.10.102:6374 \
192.168.10.103:6375 192.168.10.103:6376 \
--cluster-replicas 1
 # Docker创建集群
edis-cli --cluster create redis1:6379 redis2:6379 redis3:6379 redis4:6379 redis5:6379 redis6:6379 --cluster-replicas 1
  • --cluster:构建集群环境的所有 Redis 节点 IP + PORT 信息
  • --cluster-replicas 1:主节点数/从节点数的比例,使用一比一的比例,6 个节点最终会产生 3 主 3 从的集群环境

出现选择提示信息,输入 yes,结果如下所示,集群创建成功:

检查集群状态

任意一个节点即可,运行以下命令。

 # 查看状态
/usr/local/redis/bin/redis-cli -a 123456 --cluster check 192.168.10.101:6371

# 主节点信息
192.168.10.101:6371 (a1cd39d2...) -> 0 keys | 5461 slots | 1 slaves.
192.168.10.103:6375 (4568a256...) -> 0 keys | 5461 slots | 1 slaves.
192.168.10.102:6373 (4f3ec684...) -> 0 keys | 5462 slots | 1 slaves.
# 主节点有多少 Key
[OK] 0 keys in 3 masters.
# 每个槽的平均分配情况
0.00 keys per slot on average.
# 集群状态检查操作由 192.168.10.101:6371 节点执行
>>> Performing Cluster Check (using node 192.168.10.101:6371)
# 主节点信息以及附加的从节点个数
M: a1cd39d24bd2c9456026d592f0fac8728cea0e29 192.168.10.101:6371
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 4568a2560a688898a5d2337bce3a288f12355ae8 192.168.10.103:6375
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
# 从节点信息以及复制的主节点 ID
S: 78559b214c41f5909a5c6a0eecb9f01ee9efba9d 192.168.10.101:6372
   slots: (0 slots) slave
   replicates 4568a2560a688898a5d2337bce3a288f12355ae8
S: 3404010f04dff5109ddafaee2fe1f4abe1bc8292 192.168.10.103:6376
   slots: (0 slots) slave
   replicates 4f3ec6842325815a21f665691c8ac7b84235b306
M: 4f3ec6842325815a21f665691c8ac7b84235b306 192.168.10.102:6373
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 9cb940409435c06d2eb0628ed342abd9378d9f6b 192.168.10.102:6374
   slots: (0 slots) slave
   replicates a1cd39d24bd2c9456026d592f0fac8728cea0e29
# 所有节点都同意槽的配置情况
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
# 所有 16384 个槽都包括在内
[OK] All 16384 slots covered.

间歇性凌云壮志,持续性混吃等死