Redis的集群的基础主从复制已经讲完啦,下面就来详细讲讲Redis的集群吧。本篇会介绍Redis集群的概述、集群的方案、原理以及不同方案的特点等。

Redis集群概述

Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的,由多个Redis节点组成的程序集。

Redis集群方案

Redis 有三种集群方案

  • 主从复制模式:主机数据更新后根据配置和策略,自动会以异步复制的方式,同步数据到备机的机制;但是这种方式,主节点的写和存储受到单机的限制; 在大型系统架构中可能还需要应用端自己实现数据分片的功能,而且发生故障的时候还需要人工手动恢复;所以这种方式现在基本被摈弃掉了,转而使用Redis官方推荐的Cluster模式。
  • Sentinel(哨兵)模式:我们知道,Redis的主从复制模式下,发生故障的时候还是需要人工干预的。所以Redis从2.8开始,正式提供了Redis Sentinel(哨兵)架构来解决这个问题;Redis Sentinel能自动完成故障发现和故障转移, 并通知应用方,从而实现真正的高可用。
  • Cluster 模式(官方推荐):Redis Cluster是Redis的分布式解决方案,在3.0版本正式推出,它采用了无中心结构、主从复制等技术,有效地解决了Redis分布式方面的需求。没有特殊说明的情况下,在本篇中Redis集群说的都是Redis Cluster模式,且本篇的Redis版本 >= 3.0。

Redis Cluster

Redis Cluster可以通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

总结来说,Redis 集群有以下两个功能:

  • 将数据自动切分(split)到多个节点的能力。
  • 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。

Reids集群实现原理

Redis集群的自动切分数据跟数据分布理论有关;在分布式系统中,将数据分散到多个节点,一方面突破了单节点内存大小的限制,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。

数据分布理论

在分布式系统中,分布式数据库需要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集

数据划分通常有哈希分区顺序分区两种方式:

分区方式 特点 相关产品
哈希分区 离散程度好,数据分布与业务无关,无法顺序访问 Redis Cluster,Cassandra,Dynamo
顺序分区 离散程度易倾斜,数据分布与业务相关,可以顺序访问 BigTable,HBase,Hypertable

Redis集群采用的是哈希分区规则,常见的哈希分区有以下几种:

(1)节点取余分区:使用特定的数据,如Redis的键或用户ID,再根据节点(运行在集群模式下的Redis服务器)的数量N使用公式:hash(key) % N计算出hash值,用来决定数据存储在哪个节点上。
(2)一致性哈希分区:实现思路是为系统中的每个节点分配一个token,范围是0~232-1,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算出哈希值,然后顺时针找到第一个遇到的token节点。
(3)虚拟槽分区(Redis集群所采用的):巧妙的使用了哈希空间,使用分散度良好的哈希函数将所有的数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,这是为了消除哈希的倾斜性,便于数据拆分和扩展。例如Redis Cluster槽的范围是0~16383。槽是集群内数据管理和迁移的基本单位,每个节点都会负责一定数量的槽。

Redis 集群数据的共享

上面了解到Redis采用的是虚拟槽分区, 其中,一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群会使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和。

集群中的每个主节点负责处理一部分哈希槽。 举个例子, 一个集群有三个哈希槽, 其中:

  • 节点 A 负责处理 0 号至 5500 号哈希槽。
  • 节点 B 负责处理 5501 号至 11000 号哈希槽。
  • 节点 C 负责处理 11001 号至 16384 号哈希槽。

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

  • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。
  • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

Redis虚拟槽分布的特点:

(1)解耦数据和节点之间的关系,简化了节点的扩容和收缩。
(2)节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。
(3)支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。

Redis集群的主从复制模型

为了在主节点子集出现故障或无法与大多数节点通信时保持可用,Redis Cluster使用到了主从复制功能,也就是主从模型,其中每个哈希槽都具有从1(主节点master本身)到N个副本(N - 1个副本是从节点 slave);Redis Cluster沿用了主从复制的大部分功能,所以主从复制功能对我们理解主从集群的实现过程是非常有帮助的,建议读者把主从复制啃得差不多了,再来仔细的理解主从集群的原理相关。

所以,现在Redis Cluster模式也会被称为主从集群

主从集群和主从复制的区别:

(1)主从复制是Redis本身的一个功能,开始的时候是为了满足故障恢复和负载均衡等需求。
(2)主从集群是利用了Redis主从复制的功能,并结合了虚拟槽分区等技术,将多个Redis节点整合成一个整体对外的服务,可以解决单机内存、并发、流量等瓶颈,是Redis的分布式解决方案。

在主从集群中,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会自动启用从节点。

比如:当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机/下线了;此时就会将A节点的从节点A1提升为主节点,继续对外提供服务。由于主从节点维护的都是同样的虚拟槽,而且主从复制功能也保证了主从节点数据的大部分相同,所以在对客户正常使用基本是无感的。

如果主节点 A 和它的从节点 A1 都宕机了,此时该集群就无法再提供服务了。

主从集群的特点

  • 每个节点都和n-1个节点通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号进行通信,即对外服务端口号加10000,且内部使用二进制协议优化传输速度和带宽。
  • 客户端与redis节点直连,不需要连接集群所有的节点,连接集群中任何一个可用节点即可。整个cluster被看做是一个整体,客户端可连接任意一个节点进行操作,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点。
  • 采用无中心结构,每一个节点都保存有这个集群所有主节点以及从节点的信息,及集群状态,每个节点都和其他节点连接。所以Redis-cluster也是一种服务端分片技术。
  • 为了增加集群的可访问性,官方推荐的方案是将节点配置成主从结构,即一个master主节点,挂n个slave从节点。如果主节点失效,Redis Cluster会根据选举算法从slave节点中选择一个上升为master节点,使得整个集群继续对外提供服务。

Redis集群的数据一致性

Redis 集群不保证数据的强一致性(strong consistency): 在特定条件下, Redis 集群可能会丢失已经被执行过的写命令。主要有两个原因造成了这种情况:

(1)异步复制
Redis Cluster使用了主从复制模型,而主从复制中就用到了异步复制的功能。其实异步复制是Redis在性能和一致性上做的权衡,假如使用的是同步复制的话,主节点处理命令的速度会大大降低。

在这种情况下,由于异步复制, Redis 集群可能会丢失已经被执行过的写命令:

客户端向集群中的A节点(主节点)发送写key1的请求,A有一个从节点A1,A并不会等A1同步完后才回复客户端写入成功,而是自身处理完后直接回复给客户端。在A处理完写入的后,由于各种原因(A崩溃或网络中断)导致A与A1的部分复制未能成功;而A节点也与集群中的其他主节点通信失败,此时集群就会将A1升为主节点;而此时A1并没有写入key1,因为复制失败了,他并不知道A节点中已经发生了这件事情。此时客户端向集群再去查key1,查询不到,这样就会丢失已经被执行过的写命令。

(2)网络分裂
网络分裂导致丢失已经被执行过的写命令通常发生在Redis各个节点在不同的网络分区的情况下,不同的网络分区,也就导致了集群中各个节点可能通信不正常。

举个例子:
假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, 而 A1 、B1 、C1 分别为三个主节点的从节点, 另外还有一个客户端 Z1 。

由于网络通信等原因,集群中发生网络分裂, 集群可能会分裂为两方, A 、C 、A1 、B1 和 C1 这五个节点在此时连接不上B,认为他们组成了一个集群;而实际上节点 B 和客户端 Z1 保持着正常的连接,并为客户端提供所有的服务。

在这个网络分裂期间, 主节点 B 仍然会接受客户端 Z1 发送的写命令:

  • 如果网络分裂出现的时间很短, 那么集群会继续正常运行;随后写入节点 B 的数据也会照常复制给 B1 。
  • 但是, 如果网络分裂出现的时间足够长, 使得大多数一方将从节点 B1 设置为新的主节点, 并使用 B1 来代替原来的主节点 B , 那么 Z1 在主节点 B 与集群通信失败期间,发送给 B 的写命令将丢失。

在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的,这样就不会因为网络分裂而丢失太对“写成功”的数据, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项:

  • 对于拥有大多数节点的一方来说, 如果一个主节点未能在节点超时时间所设定的时限内重新联系上集群, 那么集群会将这个主节点视为下线, 并使用从节点来代替这个主节点继续工作。
  • 对于拥有少数节点的一方, 如果一个主节点未能在节点超时时间所设定的时限内重新联系上集群, 那么它将停止处理写命令, 并向客户端报告错误。

Redis集群的功能限制

Redis 集群相对单机在功能上存在一些限制,需要开发人员提前了解,在使用时做好规避。

  • key 批量操作支持有限。比如: mset、mget 操作,目前只支持对具有相同 slot 值的 key 执行批量操作。对于映射为不同 slot 值的 key 由于执行 mset、mget 等操作可能存在于多个节点上,因此不被支持。
  • key 事务操作支持有限,只支持多 key 在同一节点上的事务操作,当多个key分布在不同的节点上时无法使用事务功能。
  • key 作为数据分区的最小粒度,不能将一个大的键值对象如 hash、list 等映射到不同的节点。
  • 不支持多数据库空间,单机下的Redis可以支持 16 个数据库(db0 ~ db15),集群模式下只能使用一个数据库空间,即 db0。
  • 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构
  • Redis集群部署至少需要3个及3个以上的主节点

Redis集群失效

在下面两种情况下,Redis集群会无法正常运行:

(1)某个主节点和他的所有从节点都挂了,可以理解为某个槽没有Redis节点进行维护了,集群就会无法正常运行。
(2)如果同时有一半以上的主节点宕机,那么无论这些节点有没有从节点,集群同样会无法正常运行。这就是Redis的投票机制。

Redis集群的创建

修改配置文件

使用Redis-Cluster集群,需要在配置文件配置cluster-enabled yes选项,否则创建集群会失败,报如下的错误:

[ERR] Node 10.100.1.238:34210 is not configured as a cluster node.

修改完后启动Redis实例。

集群的创建

Redis的安装和启动这里就不说了,假设我们已经启好了六个正在运行的Redis示例(Redis版本是5.0.4),那么接下来我们来演示一下怎么使用这六个实例来创建一个Redis主从集群。

去到Redis的安装目录,通过--cluster create命令来创建,需要执行以下命令:
./src/redis-cli -a 'redis密码' --cluster create 每个redis的ip+port,使用空格分隔 --cluster-replicas 0

比如,我输入的是:

./src/redis-cli -a 'passwd123' --cluster create 10.100.1.238:34210 10.100.1.238:34211 10.100.1.238:34212 10.100.1.238:34213 10.100.1.238:34214 10.100.1.238:34215 --cluster-replicas 1

--cluster-replicas 1 相关说明:

后面的数字代表为每个主节点分配对应个数的从节点;
如果你有6个节点,创建集群的时候填0,就会形成6个都是主节点的redis集群;
如果你有6个节点,创建集群的时候填1,就会形成3主3从的redis集群。

集群创建完毕后,进入某个节点,可以cluster infocluster nodes命令查看到集群信息:

[root@brdev3 redis-5.0.4]# ./src/redis-cli -a 'passwd123' -c -p 34210
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(base) [root@brdev3 redis-5.0.4]# ./src/redis-cli -a 'passwd123' -c -p 34210
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:34210> cluster nodes
b9eb237114719e7dc1e9cfcba531097a775686ca 10.100.1.238:34211@44211 master - 0 1619342616000 2 connected 5461-10922
ce8e5d0faf57353d77f334dba3b37537f9baa0ed 10.100.1.238:34212@44212 master - 0 1619342616952 3 connected 10923-16383
da932172e9d29065a7f32c293ac91430bfbbf63f 10.100.1.238:34210@44210 myself,master - 0 1619342617000 1 connected 0-5460
b502e293d82b27385abf98fce524d81ff3919463 10.100.1.238:34215@44215 slave ce8e5d0faf57353d77f334dba3b37537f9baa0ed 0 1619342617963 6 connected
9b35b72b190f3bf731093b9cae36315277e78035 10.100.1.238:34213@44213 slave da932172e9d29065a7f32c293ac91430bfbbf63f 0 1619342616000 4 connected
e23b09db0441efe088dc1e84b646a359f50b7dfd 10.100.1.238:34214@44214 slave b9eb237114719e7dc1e9cfcba531097a775686ca 0 1619342618970 5 connected
127.0.0.1:34210>
127.0.0.1:34210>
127.0.0.1:34210> cluster info
# 集群状态,ok代表正常,fail代表不正常
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:142
cluster_stats_messages_pong_sent:145
cluster_stats_messages_sent:287
cluster_stats_messages_ping_received:140
cluster_stats_messages_pong_received:142
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:287
127.0.0.1:34210>

Redis集群总结

网上看到有人说某个主节点挂掉后,Redis集群会把这个节点的槽分给其他主节点。这种说法是错误的,至少对于Redis-Cluster集群来说是非常不正确的。

前面有提到:某个槽没有Redis节点进行维护了,集群就会无法正常运行。这才是正确的,所以,在这个场景下,集群会进入失败状态:

5个主节点形成一个集群,随后关闭其中一个节点;随后查看集群的状态,会显示集群状态为fail,集群就会无法正常运行;并不会自动的将槽分配给其他节点。

Redis集群功能还是非常强大的,而且许多内部实现的细节我这里都没有讲到,如果全部细致的讲一遍的话可能连载个十几篇都说不完说不透;平常工作学习的还是要养成记笔记的习惯,把他写下来,理顺来,对形成记忆有不小的帮助。