Redis Cluster集群:分片存储,支撑海量数据与高并发
Redis Cluster集群摘要 Redis Cluster是Redis官方提供的分布式解决方案,通过数据分片(16384个哈希槽)和多主节点架构实现水平扩展。核心特性包括: 数据分片 - 自动分散数据到多个节点 多主架构 - 多个主节点并发处理请求 自动故障转移 - 主节点故障时自动切换 水平扩展 - 支持动态增减节点 集群使用Gossip协议进行节点通信,包含PING/PONG/MEET/F
·
Redis Cluster集群:分片存储,支撑海量数据与高并发
1. Redis Cluster概述
1.1 什么是Redis Cluster
Redis Cluster是Redis官方提供的分布式解决方案,通过数据分片和多主节点架构,实现了Redis在水平方向的扩展能力。它可以支撑海量数据存储和高并发访问。
1.2 Cluster的核心特性
| 特性 | 描述 | 优势 |
|---|---|---|
| 数据分片 | 自动将数据分散到多个节点 | 支撑海量数据 |
| 多主架构 | 多个主节点同时提供服务 | 高并发处理 |
| 自动故障转移 | 主节点故障时自动切换 | 高可用保障 |
| 水平扩展 | 支持动态添加删除节点 | 弹性伸缩 |
1.3 Cluster与其他方案对比
| 方案 | 数据分布 | 故障转移 | 扩展性 | 复杂度 |
|---|---|---|---|---|
| 单机Redis | 单节点 | 无 | 无 | 简单 |
| 主从+哨兵 | 主从复制 | 自动 | 读扩展 | 中等 |
| Redis Cluster | 分片存储 | 自动 | 读写扩展 | 复杂 |
2. 集群核心原理
2.1 哈希槽机制
Redis Cluster使用16384个哈希槽来分配数据:
2.2 集群拓扑
2.3 节点发现机制
2.3.1 Gossip协议
Redis Cluster使用Gossip协议进行节点间通信:
- PING消息:检测节点存活状态
- PONG消息:响应PING消息
- MEET消息:通知新节点加入
- FAIL消息:标记节点故障
2.3.2 集群状态同步
# 每个节点维护集群状态信息
127.0.0.1:7000> CLUSTER NODES
07c37dfeb235213a872192d90877d0cd55635b91 192.168.1.30:7000@17000 slave e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 0 1557296957000 6 connected
67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 192.168.1.30:7002@17002 master - 0 1557296956000 8 connected 10923-16383
3. 集群部署配置
3.1 环境规划
3.1.1 节点规划
| 节点 | IP地址 | 端口 | 角色 | 哈希槽范围 |
|---|---|---|---|---|
| redis-1 | 192.168.1.10 | 7000 | Master | 0-5460 |
| redis-2 | 192.168.1.11 | 7000 | Master | 5461-10922 |
| redis-3 | 192.168.1.12 | 7000 | Master | 10923-16383 |
| redis-4 | 192.168.1.10 | 7001 | Slave | - |
| redis-5 | 192.168.1.11 | 7001 | Slave | - |
| redis-6 | 192.168.1.12 | 7001 | Slave | - |
3.2 节点配置文件
# redis-7000.conf
port 7000
bind 192.168.1.10
daemonize yes
pidfile /var/run/redis/redis-7000.pid
logfile /var/log/redis/redis-7000.log
dir /var/lib/redis/7000
# 集群配置
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.1.10
cluster-announce-port 7000
cluster-announce-bus-port 17000
# 持久化配置
appendonly yes
appendfsync everysec
# 安全配置
requirepass cluster123456
masterauth cluster123456
# 内存配置
maxmemory 2gb
maxmemory-policy allkeys-lru
3.3 自动化部署脚本
#!/bin/bash
# deploy-cluster.sh
NODES=("192.168.1.10" "192.168.1.11" "192.168.1.12")
PORTS=(7000 7001)
# 部署单个节点
deploy_node() {
local host=$1
local port=$2
echo "在 $host:$port 上部署Redis节点..."
# 创建配置文件
cat > redis-${port}.conf << EOF
port ${port}
bind ${host}
daemonize yes
pidfile /var/run/redis/redis-${port}.pid
logfile /var/log/redis/redis-${port}.log
dir /var/lib/redis/${port}
cluster-enabled yes
cluster-config-file nodes-${port}.conf
cluster-node-timeout 15000
cluster-announce-ip ${host}
cluster-announce-port ${port}
cluster-announce-bus-port $((port + 10000))
appendonly yes
appendfsync everysec
requirepass cluster123456
masterauth cluster123456
maxmemory 2gb
maxmemory-policy allkeys-lru
EOF
# 复制配置到目标服务器
scp redis-${port}.conf root@${host}:/etc/redis/
# 创建数据目录
ssh root@${host} "mkdir -p /var/lib/redis/${port}"
# 启动Redis实例
ssh root@${host} "redis-server /etc/redis/redis-${port}.conf"
echo "节点 $host:$port 部署完成"
}
# 部署所有节点
for host in "${NODES[@]}"; do
for port in "${PORTS[@]}"; do
deploy_node $host $port
done
done
# 等待节点启动
echo "等待节点启动..."
sleep 10
# 创建集群
echo "创建Redis集群..."
redis-cli --cluster create \
192.168.1.10:7000 192.168.1.11:7000 192.168.1.12:7000 \
192.168.1.10:7001 192.168.1.11:7001 192.168.1.12:7001 \
--cluster-replicas 1 \
-a cluster123456
echo "集群部署完成!"
3.4 集群初始化
# 创建集群
redis-cli --cluster create \
192.168.1.10:7000 192.168.1.11:7000 192.168.1.12:7000 \
192.168.1.10:7001 192.168.1.11:7001 192.168.1.12:7001 \
--cluster-replicas 1 -a cluster123456
# 检查集群状态
redis-cli --cluster check 192.168.1.10:7000 -a cluster123456
# 查看集群信息
redis-cli -c -h 192.168.1.10 -p 7000 -a cluster123456 cluster info
4. 数据分片机制
4.1 哈希槽分配算法
public class RedisClusterSlotCalculator {
private static final int CLUSTER_SLOTS = 16384;
/**
* 计算key对应的哈希槽
*/
public static int calculateSlot(String key) {
// 检查是否有哈希标签
int start = key.indexOf('{');
int end = key.indexOf('}', start + 1);
String hashKey;
if (start != -1 && end != -1 && end > start + 1) {
// 使用哈希标签内的内容计算
hashKey = key.substring(start + 1, end);
} else {
// 使用完整key计算
hashKey = key;
}
return crc16(hashKey.getBytes()) % CLUSTER_SLOTS;
}
/**
* CRC16算法实现
*/
private static int crc16(byte[] bytes) {
int crc = 0x0000;
int polynomial = 0x1021;
for (byte b : bytes) {
for (int i = 0; i < 8; i++) {
boolean bit = ((b >> (7 - i) & 1) == 1);
boolean c15 = ((crc >> 15 & 1) == 1);
crc <<= 1;
if (c15 ^ bit) {
crc ^= polynomial;
}
}
}
return crc & 0xFFFF;
}
}
4.2 哈希标签使用
@Service
public class ClusterKeyDesignService {
/**
* 使用哈希标签确保相关数据在同一节点
*/
public void demonstrateHashTags() {
// 用户相关的所有数据使用相同标签
String userId = "1001";
// 这些key会分配到同一个哈希槽
String profileKey = "user:{" + userId + "}:profile";
String settingsKey = "user:{" + userId + "}:settings";
String preferencesKey = "user:{" + userId + "}:preferences";
System.out.println("使用哈希标签的key分布:");
System.out.println(profileKey + " -> slot: " +
RedisClusterSlotCalculator.calculateSlot(profileKey));
System.out.println(settingsKey + " -> slot: " +
RedisClusterSlotCalculator.calculateSlot(settingsKey));
System.out.println(preferencesKey + " -> slot: " +
RedisClusterSlotCalculator.calculateSlot(preferencesKey));
}
}
5. 故障处理与扩容
5.1 故障检测机制
5.2 集群扩容
#!/bin/bash
# add-node.sh
NEW_NODE_HOST="192.168.1.13"
NEW_NODE_PORT="7000"
EXISTING_NODE="192.168.1.10:7000"
PASSWORD="cluster123456"
echo "添加新主节点..."
# 启动新节点
ssh root@${NEW_NODE_HOST} "redis-server /etc/redis/redis-${NEW_NODE_PORT}.conf"
# 添加节点到集群
redis-cli --cluster add-node \
${NEW_NODE_HOST}:${NEW_NODE_PORT} \
${EXISTING_NODE} \
-a ${PASSWORD}
# 重新分配哈希槽
echo "重新分配哈希槽..."
redis-cli --cluster reshard ${EXISTING_NODE} \
--cluster-from all \
--cluster-to $(redis-cli -h ${NEW_NODE_HOST} -p ${NEW_NODE_PORT} -a ${PASSWORD} cluster myid) \
--cluster-slots 4096 \
--cluster-yes \
-a ${PASSWORD}
echo "集群扩容完成!"
6. Java应用集成
6.1 Lettuce集群配置
@Configuration
public class RedisClusterConfig {
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.password}")
private String password;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 解析集群节点
List<String> nodes = Arrays.asList(clusterNodes.split(","));
Set<RedisNode> redisNodes = nodes.stream()
.map(node -> {
String[] hostPort = node.trim().split(":");
return new RedisNode(hostPort[0], Integer.parseInt(hostPort[1]));
})
.collect(Collectors.toSet());
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
clusterConfig.setClusterNodes(redisNodes);
clusterConfig.setPassword(password);
clusterConfig.setMaxRedirects(3);
return new LettuceConnectionFactory(clusterConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// 序列化配置
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
6.2 集群操作服务
@Service
public class ClusterOperationService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 使用哈希标签的批量操作
*/
public void batchOperationWithHashTag(String userId, Map<String, Object> userData) {
// 使用哈希标签确保所有操作在同一节点
String profileKey = "user:{" + userId + "}:profile";
String settingsKey = "user:{" + userId + "}:settings";
String preferencesKey = "user:{" + userId + "}:preferences";
// 可以使用事务,因为都在同一节点
redisTemplate.multi();
redisTemplate.opsForHash().putAll(profileKey, userData);
redisTemplate.opsForValue().set(settingsKey, userData.get("settings"));
redisTemplate.opsForValue().set(preferencesKey, userData.get("preferences"));
redisTemplate.exec();
}
/**
* 跨节点数据收集
*/
public Map<String, Object> collectDataFromAllNodes(List<String> keys) {
Map<String, Object> result = new HashMap<>();
for (String key : keys) {
try {
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
result.put(key, value);
}
} catch (Exception e) {
// 处理节点故障或网络问题
result.put(key, "ERROR: " + e.getMessage());
}
}
return result;
}
}
7. 运维监控
7.1 集群监控服务
@Service
public class ClusterMonitoringService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 获取集群整体状态
*/
public ClusterStatus getClusterStatus() {
return redisTemplate.execute((RedisCallback<ClusterStatus>) connection -> {
Properties clusterInfo = connection.clusterInfo();
ClusterStatus status = new ClusterStatus();
status.setState(clusterInfo.getProperty("cluster_state"));
status.setSlotsAssigned(Integer.parseInt(clusterInfo.getProperty("cluster_slots_assigned", "0")));
status.setSlotsOk(Integer.parseInt(clusterInfo.getProperty("cluster_slots_ok", "0")));
status.setKnownNodes(Integer.parseInt(clusterInfo.getProperty("cluster_known_nodes", "0")));
return status;
});
}
/**
* 健康检查
*/
@Scheduled(fixedRate = 30000)
public void performHealthCheck() {
try {
ClusterStatus status = getClusterStatus();
if (!"ok".equals(status.getState())) {
sendAlert("集群状态异常", "当前状态: " + status.getState());
}
if (status.getSlotsAssigned() != 16384) {
sendAlert("哈希槽分配不完整",
"已分配槽数: " + status.getSlotsAssigned() + "/16384");
}
} catch (Exception e) {
sendAlert("健康检查失败", e.getMessage());
}
}
private void sendAlert(String title, String message) {
System.err.println("CLUSTER ALERT: " + title + " - " + message);
}
}
8. 最佳实践
8.1 架构设计建议
8.1.1 节点数量规划
- 最少6个节点:3主3从保证高可用
- 奇数个主节点:便于选举决策
- 每主配一从:保证故障转移能力
8.1.2 硬件配置
| 角色 | CPU | 内存 | 存储 | 网络 |
|---|---|---|---|---|
| 主节点 | 8核+ | 16GB+ | SSD | 千兆+ |
| 从节点 | 4核+ | 8GB+ | SSD | 千兆+ |
8.2 性能优化
8.2.1 合理使用哈希标签
// ✅ 推荐:相关数据使用相同标签
String userPrefix = "{user:" + userId + "}";
String profileKey = userPrefix + ":profile";
String settingsKey = userPrefix + ":settings";
// ❌ 避免:无关数据使用相同标签
String mixedKey = "{common}:user:1001:profile"; // 会导致数据倾斜
8.2.2 避免大key和热key
@Service
public class KeyOptimizationService {
/**
* 拆分大key
*/
public void splitLargeHash(String largeKey, Map<String, String> data) {
// 将大hash拆分为多个小hash
int chunkSize = 1000;
int chunkIndex = 0;
Map<String, String> chunk = new HashMap<>();
for (Map.Entry<String, String> entry : data.entrySet()) {
chunk.put(entry.getKey(), entry.getValue());
if (chunk.size() >= chunkSize) {
String chunkKey = largeKey + ":chunk:" + chunkIndex++;
redisTemplate.opsForHash().putAll(chunkKey, chunk);
chunk.clear();
}
}
// 处理剩余数据
if (!chunk.isEmpty()) {
String chunkKey = largeKey + ":chunk:" + chunkIndex;
redisTemplate.opsForHash().putAll(chunkKey, chunk);
}
}
}
8.3 运维建议
8.3.1 监控关键指标
- 集群状态:cluster_state必须为ok
- 哈希槽分配:16384个槽全部分配
- 节点通信:cluster_known_nodes数量正确
- 内存使用:各节点内存使用均衡
8.3.2 容灾规划
#!/bin/bash
# cluster-backup.sh
CLUSTER_NODES=("192.168.1.10:7000" "192.168.1.11:7000" "192.168.1.12:7000")
BACKUP_DIR="/backup/redis-cluster/$(date +%Y%m%d)"
# 创建备份目录
mkdir -p ${BACKUP_DIR}
# 备份每个主节点
for node in "${CLUSTER_NODES[@]}"; do
host=$(echo $node | cut -d: -f1)
port=$(echo $node | cut -d: -f2)
echo "备份节点 $node..."
redis-cli -h $host -p $port -a cluster123456 BGSAVE
# 等待备份完成
sleep 10
# 复制RDB文件
scp root@${host}:/var/lib/redis/${port}/dump.rdb \
${BACKUP_DIR}/dump-${host}-${port}.rdb
done
echo "集群备份完成!"
总结
Redis Cluster提供了完整的分布式Redis解决方案:
核心知识点
- 数据分片:通过16384个哈希槽实现数据自动分片
- 多主架构:多个主节点同时提供读写服务
- 自动故障转移:主节点故障时从节点自动接管
- 水平扩展:支持动态添加删除节点
- Java集成:完善的Spring Boot集成支持
关键要点
- 哈希槽机制:数据分片的核心,支持一致性哈希
- 节点通信:基于Gossip协议的集群状态同步
- 故障检测:分布式故障检测和自动恢复
- 客户端路由:智能路由和重定向机制
最佳实践
- 合理规划架构:至少6节点,主从配对部署
- 优化数据分布:使用哈希标签,避免数据倾斜
- 监控集群状态:实时监控节点和哈希槽状态
- 制定运维规范:建立完善的备份和恢复流程
- 性能调优:避免大key和热key问题
Redis Cluster是构建大规模Redis架构的最佳选择,为海量数据和高并发提供了可靠的解决方案。
下一篇预告:《Redis性能优化实战:从原理到实践,让Redis跑更快》
更多推荐


所有评论(0)