在 Redis 3.0 之前,实现集群通常依赖于哨兵(Sentinel)工具来监控主节点的状态。当主节点异常时,哨兵会触发主从切换,将某个从节点晋升为新的主节点。然而,哨兵模式存在一些挑战,包括配置较为繁琐、性能和高可用性表现一般等问题。
特别是在主从切换的瞬间,可能存在访问中断的情况,这对于需要持续服务的应用来说是不可接受的。此外,哨兵模式限制了对外提供服务的主节点数量,无法支持很高的并发。同时,单个主节点内存设置过大可能导致持久化文件庞大,从而影响数据的恢复和主从同步的效率。
为了解决这些问题,Redis 在 3.0 版本引入了新一代的集群模式。这个集群模式克服了哨兵模式的一些限制,支持更好的水平扩展、高可用性和性能。它允许数据分布在多个节点上,每个节点都可以对外提供服务,避免了单一主节点的瓶颈。集群模式还支持自动分片,提高了系统的性能。升级到 Redis 集群模式可以带来更好的水平扩展性和高可用性,尤其适用于需要处理大量并发请求的应用场景。
Redis 集群是一个由多个主从节点组成的分布式服务器群,它具备复制、高可用和分片等特性。与之前版本的哨兵模式相比,Redis 集群在性能和高可用性方面均有显著优势。不同于哨兵模式,Redis 集群不需要使用哨兵来完成节点移除和故障转移的功能。每个节点都需要被设置成集群模式,这种模式没有中心节点的概念,可以水平扩展,根据官方文档的称述,甚至可以线性扩展到上万个节点(尽管官方推荐不超过 1000 个节点)。
Redis 集群的配置相较于哨兵模式更为简单,其设计理念使得它更适合大规模的分布式应用。这个集群模式的灵活性和水平扩展性为系统的性能提供了更多的可能性。整体而言,采用 Redis 集群能够为应用提供更稳定、高效的服务,尤其在处理大规模并发请求和维护高可用性方面具备显著的优势。
Redis 集群需要至少三个 Master 节点,以三个为例。
搭建 Master 节点,并且给每个 Master 各搭建一个 Slave 节点,总共 6 个,使用三台服务器部署,每台机器一主一从:
# 在/usr/local下创建redis-cluster文件夹
mkdir -p /usr/local/redis-cluster
# 在redis-cluster下分别创建8001和8004文件夹
mkdir /usr/local/redis-cluster/8001
mkdir /usr/local/redis-cluster/8004
将之前的 redis.conf 配置文件复制到 8001 下,然后修改如下内容:
# redis-cluster/8001/redis.conf
daemonize yes
port 8001
pidfile /var/run/redis_8001.pid
dir /usr/local/redis-cluster/8001/
cluster-enabled yes
cluster-config-file nodes-8001.conf
cluster-node-timeout 10000
# bind 127.0.0.1(注释掉bind,内网一般可以不配置)
protected-mode no
appendonly yes
requirepass autumn
masterauth autumn
# 在8001的目录下执行以下命令
sed -i 's/8001/8004/g' /usr/local/redis-cluster/8001/redis.conf
# 启动6个redis实例
/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/800*/redis.conf
# 检查是否启动成功
ps -ef | grep redis
# 创建整个redis集群
/usr/local/redis-5.0.3/src/redis-cli -a autumn --cluster create --cluster-replicas 1 192.168.0.61:8001 192.168.0.62:8002 192.168.0.63:8003 192.168.0.61:8004 192.168.0.62:8005 192.168.0.63:8006
# 连接任意一个客户端
/usr/local/redis-5.0.3/src/redis-cli -a autumn -c -h 192.168.0.61 -p 800*
# 进行验证: cluster info(查看集群信息)、cluster nodes(查看节点列表)
# 进行数据操作验证
# 关闭集群
/usr/local/redis-5.0.3/src/redis-cli -a autumn -c -h 192.168.0.60 -p 800* shutdown
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.0version>
dependency>
public class JedisClusterTest {
public static void main(String[] args) throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.0.61", 8001));
jedisClusterNode.add(new HostAndPort("192.168.0.62", 8002));
jedisClusterNode.add(new HostAndPort("192.168.0.63", 8003));
jedisClusterNode.add(new HostAndPort("192.168.0.61", 8004));
jedisClusterNode.add(new HostAndPort("192.168.0.62", 8005));
jedisClusterNode.add(new HostAndPort("192.168.0.63", 8006));
JedisCluster jedisCluster = null;
try {
//connectionTimeout:连接一个 url 的连接等待时间
//soTimeout:连接上一个 url,获取 response 的返回等待时间
jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "autumn", config);
System.out.println(jedisCluster.set("cluster", "autumn"));
System.out.println(jedisCluster.get("cluster"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCluster != null)
jedisCluster.close();
}
}
}
运行结果:
OK
autumn
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
server:
port: 8080
spring:
redis:
database: 0
timeout: 3000
password: autumn
cluster:
nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.63:8006
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
@RestController
public class IndexController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test_cluster")
public void testCluster() throws InterruptedException {
stringRedisTemplate.opsForValue().set("autumn", "666");
System.out.println(stringRedisTemplate.opsForValue().get("autumn"));
}
}
Redis Cluster 的原理是基于槽位划分的分布式架构。在这个集群中,所有数据被分成 16384 个槽位,每个节点负责管理其中的一部分。每个节点存储了整个集群槽位的信息。
当客户端连接到 Redis Cluster 时,即会获取一份集群的槽位配置信息,并将其缓存在客户端本地。当客户端需要访问某个特定的 key 时,便可直接定位到负责该槽位的目标节点,实现了高效的数据定位。
由于槽位信息可能会在客户端与服务器之间不一致,Redis Cluster 还实现了校验和调整机制,以确保槽位信息的一致性。
这种基于槽位划分的方式使得 Redis Cluster 具有良好的水平扩展性,每个节点只需要管理一部分槽位,使得集群能够应对大规模数据和高并发的场景。
Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。
HASH_SLOT = CRC16(key) mod 16384
当客户端向一个错误的节点发出了指令,该节当客户端向一个错误的节点发送指令时,该节点会察觉到指令涉及的 key 并不在自己负责的槽位上。这时节点会向客户端发送一条特殊的跳转指令,带有正确节点的地址,告知客户端去连接这个节点以执行操作。客户端接收到这个指令后,不仅会切换到正确的节点执行操作,还会更新本地的槽位映射表缓存,确保后续的所有 key 都使用新的槽位映射表。这个过程实现了客户端与集群之间的自动纠正机制,确保了数据的准确获取。
Redis 集群节点之间采用 gossip 协议进行通信,以维护集群的元数据(包括节点信息、主从角色、节点数量、共享数据等)。
有两种元数据维护方式:
采用 gossip 协议的通信机制能够实现分布式集群的信息同步,通过节点之间的消息交换,维护集群的一致性和稳定性。
网络抖动是指网络连接在短时间内发生不稳定的波动,有时连接可能中断,然后又迅速恢复正常。在机房网络中,这样的情况很常见。对于 Redis Cluster 这样的分布式系统,这种抖动可能导致节点之间的通信问题。
为了处理这种情况,Redis Cluster 提供了 cluster-node-timeout 这个选项。这个选项定义了一个节点持续失联的时间阈值。只有当某个节点在一定时间内持续失联,系统才会认为该节点出现了故障,需要进行主从切换。
如果没有这个选项,网络抖动可能导致节点频繁地被认为是故障节点,触发主从切换。这会导致数据频繁地重新复制,增加系统的负担和延迟。
通过这个机制,Redis集群能够在主节点故障时快速、安全地进行主从切换,确保系统的高可用性。
在 Redis 集群中,如果没有足够的机制来确保半数以上的节点参与写服务,可能发生脑裂问题。脑裂是指网络分区发生后,多个主节点继续对外提供写服务,导致数据不一致。当网络分区解决后,其中一个主节点可能被变为从节点,这时会发生大量数据丢失。
为了规避这个问题,可以在 Redis 配置中添加一些参数。例如:
min-replicas-to-write 1
这个参数表示写数据成功所需的最少同步从节点数量。这个数量可以设置为模仿大于半数机制的配置。比如,如果集群总共有三个节点,可以设置为 1,再加上 leader 节点就是 2,这超过了半数。
这样的配置在一定程度上可能会影响集群的可用性。如果从节点少于 1 个,即使 leader 节点正常,集群也无法提供服务。在具体场景中,需要权衡选择,根据可靠性和可用性的要求进行配置。
在 Redis 集群中,有一个配置项叫做 cluster-require-full-coverage,值分为两种:
这个配置的选择通常取决于对可用性和一致性的需求。若对可用性要求比较高选"no";若对一致性要求比较高选"yes"。
在 Redis 集群中,有一个关键的机制是进行新的主节点选举。这个选举过程需要大于半数的集群主节点的同意才能成功。现在我们来看一下为什么需要至少三个master节点:
总的来说,三个节点是为了确保基本的故障容忍,而奇数个节点则更有利于在选举等场景中更容易达到大多数同意的条件
在 Redis 集群中,有一些批量操作命令,如 mset(设置多个key的值)、mget(获取多个key的值)等。但是需要注意的是,对于这些操作,Redis 集群只支持所有的 key 都位于同一个 slot 槽位的情况。而如果有多个 key,确保它们都落在同一个 slot 槽位可能有些困难。
为了解决这个问题,可以使用一种特殊的写法。在key的前面加上大括号 {},比如 {user1}:1:name
。这样 Redis 在计算 hash slot 时只会考虑大括号里的值,而不考虑其他部分。这就确保了不同的 key 在计算 slot 时会得到相同的结果,最终它们会被映射到同一个 slot 槽位上。
举个例子,假设 name
和 age
计算的 hash slot 值不一样,但是通过在命令中使用 {user1}
,Redis 只会用这个大括号里的值进行 hash slot 计算,因此这两个 key 最终都能落在同一个 slot 槽位上。这种写法确保了在集群中执行这样的批量操作时,数据能够被正确映射到同一个 slot 槽位,保证了操作的一致性。
通过这个机制,Redis 集群能够在主节点故障时快速、安全地进行主从切换,确保系统的高可用性。不过为了高可用一般都推荐至少部署三个哨兵节点。推荐奇数个哨兵节点的原理跟集群奇数个 Master 节点类似。