前言
在大数据与高并发场景下,单节点 Redis 的容量与可用性已无法满足需求。Redis 通过集群与分布式技术,实现了数据的分片存储与高可用部署,成为分布式系统的核心组件。本文将深入解析 Redis 集群的底层原理、架构模式与实战经验,结合代码示例与最佳实践,帮助开发者构建高性能、高可用的分布式缓存系统。
一、集群基础架构与核心原理
1. 数据分片机制
Redis 集群采用 哈希槽(Hash Slot) 实现数据分片,共有 16384 个槽:
- 每个节点负责一部分哈希槽(如节点 A 负责 0-5460)
- 键通过
CRC16(key) % 16384
计算哈希槽位置 - 支持在线扩容 / 缩容(通过迁移哈希槽实现)
# 查看集群节点与槽分布
redis-cli -c -h 127.0.0.1 -p 7000 CLUSTER NODES
2. 节点通信机制
- Gossip 协议:节点间通过定期交换消息(ping/pong)维护集群状态
- 消息内容:包含节点 ID、IP、端口、负责的哈希槽、状态等
- 故障检测:通过节点间的消息交换发现下线节点并触发故障转移
3. 客户端路由
客户端直接与目标节点通信,无需通过代理:
- 客户端缓存哈希槽 - 节点映射关系
- 当访问的键不在当前节点时,节点返回
MOVED
或ASK
重定向 - 客户端自动更新槽 - 节点映射(需定期刷新)
二、集群部署模式详解
1. 主从复制模式
核心架构:
- 一个主节点(Master)处理写操作
- 多个从节点(Slave)同步主节点数据,处理读请求
- 主节点挂掉后,需手动将从节点提升为主节点
配置示例:
# 主节点配置(master.conf)
port 6379
bind 0.0.0.0
# 从节点配置(slave.conf)
port 6380
slaveof 127.0.0.1 6379
优缺点:
- 优点:实现简单,读写分离提升性能
- 缺点:主节点单点故障,扩容困难
2. 哨兵模式(Sentinel)
核心组件:
- 哨兵节点:监控 Redis 节点状态,执行故障转移
- Redis 节点:包含主节点和从节点
工作流程:
- 哨兵集群发现并监控所有 Redis 节点
- 当主节点下线时,哨兵集群选举新主节点
- 从节点自动切换到新主节点
配置示例:
# sentinel.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2 # 2个哨兵同意才触发故障转移
sentinel down-after-milliseconds mymaster 5000 # 主节点5秒无响应视为下线
sentinel failover-timeout mymaster 10000 # 故障转移超时时间
优缺点:
- 优点:自动故障转移,高可用性
- 缺点:不支持自动分片,数据量受限于单节点
3. 集群模式(Cluster)
核心特性:
- 自动分片:16384 个哈希槽分布在多个节点
- 高可用:每个主节点有 1 个或多个从节点
- 自动故障转移:主节点下线后,从节点自动晋升为主节点
部署步骤:
- 准备节点配置:
# 节点配置(如7000.conf) port 7000 cluster-enabled yes # 开启集群模式 cluster-config-file nodes.conf # 集群配置文件 cluster-node-timeout 15000 # 节点超时时间 appendonly yes
- 启动节点:
redis-server 7000.conf redis-server 7001.conf ...
- 创建集群:
redis-cli --cluster create \ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ --cluster-replicas 1 # 每个主节点1个从节点
三、集群操作实战
1. 集群状态查看
# 查看集群信息
redis-cli -c -h 127.0.0.1 -p 7000 CLUSTER INFO
# 查看节点列表
redis-cli -c -h 127.0.0.1 -p 7000 CLUSTER NODES
2. 数据操作示例
# 向集群写入数据(自动路由到对应节点)
redis-cli -c -h 127.0.0.1 -p 7000 SET user:1001 "Alice"
# 读取数据
redis-cli -c -h 127.0.0.1 -p 7000 GET user:1001
3. 集群扩容
# 1. 启动新节点(7006)
redis-server 7006.conf
# 2. 将新节点加入集群
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
# 3. 分配哈希槽(从其他节点迁移部分槽到新节点)
redis-cli --cluster reshard 127.0.0.1:7000
4. 故障处理
# 模拟主节点(如7000)下线
redis-cli -h 127.0.0.1 -p 7000 SHUTDOWN
# 查看集群状态(7003会自动晋升为主节点)
redis-cli -c -h 127.0.0.1 -p 7001 CLUSTER NODES
# 恢复原主节点(作为从节点加入集群)
redis-server 7000.conf
redis-cli -c -h 127.0.0.1 -p 7000 CLUSTER REPLICATE <新主节点ID>
四、分布式锁实现
1. 基础分布式锁(RedLock 算法)- Python代码示例
# Python实现RedLock算法
import redis
import time
import uuid
def acquire_lock(redis_nodes, resource, expiration=10):
lock_value = str(uuid.uuid4()) # 唯一标识锁
start_time = time.time()
# 尝试在多数节点获取锁
acquired_nodes = []
for node in redis_nodes:
if node.set(resource, lock_value, nx=True, ex=expiration):
acquired_nodes.append(node)
# 判断是否在多数节点获取成功
elapsed_time = time.time() - start_time
if len(acquired_nodes) >= (len(redis_nodes) // 2 + 1) and \
elapsed_time < expiration:
return lock_value
else:
# 获取失败,释放已获取的锁
for node in acquired_nodes:
node.delete(resource)
return False
def release_lock(redis_nodes, resource, lock_value):
for node in redis_nodes:
# 使用Lua脚本原子性验证并删除锁
script = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
"""
node.eval(script, 1, resource, lock_value)
2. 基础分布式锁(RedLock 算法)- Java 代码示例
// Java实现RedLock算法
import redis.clients.jedis.Jedis;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class RedLock {
private List<Jedis> jedisInstances;
private int quorum; // 法定数量
public RedLock(List<Jedis> jedisInstances) {
this.jedisInstances = jedisInstances;
this.quorum = (jedisInstances.size() / 2) + 1;
}
public String lock(String resource, long expirationTime) {
String lockValue = UUID.randomUUID().toString();
int acquiredCount = 0;
long startTime = System.currentTimeMillis();
// 尝试在所有节点获取锁
for (Jedis jedis : jedisInstances) {
try {
// 使用SET命令的NX和EX选项原子性获取锁
String result = jedis.set(resource, lockValue, "NX", "EX", expirationTime);
if ("OK".equals(result)) {
acquiredCount++;
}
} catch (Exception e) {
// 记录异常但继续尝试其他节点
e.printStackTrace();
}
// 如果已超过过期时间,放弃获取锁
if (System.currentTimeMillis() - startTime > expirationTime) {
break;
}
}
// 判断是否获取多数节点的锁
if (acquiredCount >= quorum) {
return lockValue;
} else {
// 获取失败,释放已获取的锁
unlock(resource, lockValue);
return null;
}
}
public void unlock(String resource, String lockValue) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +<