集群模式概述
Redis Cluster 是 Redis 的分布式实现,它将数据分片存储在多个节点上,提供了水平扩展能力和高可用性。集群模式通过一致性哈希算法将数据分布到不同的节点,每个节点负责一部分数据槽(slot),同时支持主从复制和自动故障转移。
Redis 集群架构
特点:数据分片、水平扩展、自动故障转移、无中心架构
🔧 集群配置
配置 Redis 集群的详细步骤:
节点配置文件:
redis-7001.conf(主节点配置):
# 基本配置 port 7001 bind 0.0.0.0 # 启用集群模式 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 # 集群要求至少3个主节点 cluster-require-full-coverage no # 密码配置 requirepass mypassword masterauth mypassword # 持久化配置 appendonly yes appendfilename "appendonly-7001.aof" # 日志配置 logfile "/var/log/redis/redis-7001.log" loglevel notice # 工作目录 dir /var/lib/redis/7001 # 内存配置 maxmemory 1gb maxmemory-policy allkeys-lru # 网络配置 tcp-keepalive 300 timeout 0
创建集群配置脚本:
#!/bin/bash # 创建Redis集群配置脚本 # 创建目录 mkdir -p /var/lib/redis/{7001,7002,7003,7004,7005,7006} mkdir -p /var/log/redis # 端口列表 ports=(7001 7002 7003 7004 7005 7006) # 生成配置文件 for port in "${ports[@]}"; do cat > /etc/redis/redis-${port}.conf << EOF port ${port} bind 0.0.0.0 cluster-enabled yes cluster-config-file nodes-${port}.conf cluster-node-timeout 15000 cluster-require-full-coverage no requirepass mypassword masterauth mypassword appendonly yes appendfilename "appendonly-${port}.aof" logfile "/var/log/redis/redis-${port}.log" loglevel notice dir /var/lib/redis/${port} maxmemory 1gb maxmemory-policy allkeys-lru tcp-keepalive 300 timeout 0 EOF done echo "配置文件生成完成"
启动集群节点:
# 启动所有节点 redis-server /etc/redis/redis-7001.conf redis-server /etc/redis/redis-7002.conf redis-server /etc/redis/redis-7003.conf redis-server /etc/redis/redis-7004.conf redis-server /etc/redis/redis-7005.conf redis-server /etc/redis/redis-7006.conf # 或者使用循环启动 for port in {7001..7006}; do redis-server /etc/redis/redis-${port}.conf & done
创建集群:
# 使用 redis-cli 创建集群 redis-cli --cluster create \ 192.168.1.101:7001 \ 192.168.1.102:7002 \ 192.168.1.103:7003 \ 192.168.1.104:7004 \ 192.168.1.105:7005 \ 192.168.1.106:7006 \ --cluster-replicas 1 \ -a mypassword # 参数说明: # --cluster-replicas 1: 每个主节点有1个从节点 # -a mypassword: 集群密码
🎯 数据分片机制
Redis 集群使用哈希槽(Hash Slot)进行数据分片:
1. 键名
user:1001
→
2. CRC16
计算哈希值
→
3. 取模
% 16384
→
4. 槽位
Slot 8765
→
5. 节点
Master 2
💡 哈希槽特点:
- 总槽数:16384个槽位(0-16383)
- 分片算法:CRC16(key) % 16384
- 槽位分配:每个主节点负责一部分槽位
- 数据迁移:通过槽位迁移实现扩容缩容
哈希标签(Hash Tag):
# 使用哈希标签确保相关键在同一节点 SET user:{1001}:profile "John Doe" SET user:{1001}:settings "theme:dark" SET user:{1001}:posts "post1,post2,post3" # 所有 user:{1001}:* 的键都会在同一个节点 # 因为哈希计算只使用 {1001} 部分
节点 | 槽位范围 | 槽位数量 | 数据比例 |
---|---|---|---|
Master 1 | 0 - 5460 | 5461 | 33.3% |
Master 2 | 5461 - 10922 | 5462 | 33.3% |
Master 3 | 10923 - 16383 | 5461 | 33.4% |
🛠️ 集群管理命令
Redis 集群的管理和维护命令:
集群信息查看
# 查看集群信息 CLUSTER INFO # 查看集群节点 CLUSTER NODES # 查看槽位分配 CLUSTER SLOTS # 查看特定键的槽位 CLUSTER KEYSLOT user:1001
节点管理
# 添加节点 CLUSTER MEET 192.168.1.107 7007 # 删除节点 CLUSTER FORGET node-id # 设置从节点 CLUSTER REPLICATE master-node-id # 故障转移 CLUSTER FAILOVER
槽位管理
# 分配槽位 CLUSTER ADDSLOTS 0 1 2 3 4 # 删除槽位 CLUSTER DELSLOTS 0 1 2 3 4 # 迁移槽位 CLUSTER SETSLOT 100 MIGRATING target-node-id CLUSTER SETSLOT 100 IMPORTING source-node-id
数据迁移
# 获取槽位中的键 CLUSTER GETKEYSINSLOT 100 10 # 迁移键 MIGRATE 192.168.1.107 7007 key 0 5000 # 批量迁移 MIGRATE 192.168.1.107 7007 "" 0 5000 KEYS key1 key2 key3
集群管理脚本:
#!/bin/bash # Redis 集群管理脚本 CLUSTER_NODES=("192.168.1.101:7001" "192.168.1.102:7002" "192.168.1.103:7003") PASSWORD="mypassword" # 检查集群状态 check_cluster_status() { echo "=== 集群状态检查 ===" for node in "${CLUSTER_NODES[@]}"; do echo "检查节点: $node" redis-cli -h ${node%:*} -p ${node#*:} -a $PASSWORD CLUSTER INFO | grep cluster_state done } # 查看节点信息 show_cluster_nodes() { echo "=== 集群节点信息 ===" redis-cli -h ${CLUSTER_NODES[0]%:*} -p ${CLUSTER_NODES[0]#*:} -a $PASSWORD CLUSTER NODES } # 查看槽位分配 show_slot_allocation() { echo "=== 槽位分配信息 ===" redis-cli -h ${CLUSTER_NODES[0]%:*} -p ${CLUSTER_NODES[0]#*:} -a $PASSWORD CLUSTER SLOTS } # 添加新节点 add_node() { local new_node=$1 local existing_node=${CLUSTER_NODES[0]} echo "添加节点 $new_node 到集群" redis-cli --cluster add-node $new_node $existing_node -a $PASSWORD } # 重新分片 reshard_cluster() { local existing_node=${CLUSTER_NODES[0]} echo "开始重新分片" redis-cli --cluster reshard $existing_node -a $PASSWORD } # 执行函数 case $1 in "status") check_cluster_status ;; "nodes") show_cluster_nodes ;; "slots") show_slot_allocation ;; "add") add_node $2 ;; "reshard") reshard_cluster ;; *) echo "用法: $0 {status|nodes|slots|add|reshard}" ;; esac
📱 客户端连接
应用程序如何连接到 Redis 集群:
Java 客户端示例(Jedis):
import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPoolConfig; import java.util.HashSet; import java.util.Set; public class ClusterExample { public static void main(String[] args) { // 配置集群节点 SetclusterNodes = new HashSet<>(); clusterNodes.add(new HostAndPort("192.168.1.101", 7001)); clusterNodes.add(new HostAndPort("192.168.1.102", 7002)); clusterNodes.add(new HostAndPort("192.168.1.103", 7003)); // 连接池配置 JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(100); poolConfig.setMaxIdle(10); poolConfig.setMinIdle(5); // 创建集群连接 JedisCluster jedisCluster = new JedisCluster( clusterNodes, 2000, // 连接超时 2000, // 读取超时 5, // 最大重定向次数 "mypassword", // 密码 poolConfig ); try { // 使用集群 jedisCluster.set("user:1001", "John Doe"); String value = jedisCluster.get("user:1001"); System.out.println("Value: " + value); // 使用哈希标签 jedisCluster.set("user:{1001}:profile", "profile data"); jedisCluster.set("user:{1001}:settings", "settings data"); } finally { jedisCluster.close(); } } }
Python 客户端示例(redis-py-cluster):
from rediscluster import RedisCluster # 配置集群节点 startup_nodes = [ {"host": "192.168.1.101", "port": "7001"}, {"host": "192.168.1.102", "port": "7002"}, {"host": "192.168.1.103", "port": "7003"} ] # 创建集群连接 rc = RedisCluster( startup_nodes=startup_nodes, password='mypassword', decode_responses=True, skip_full_coverage_check=True ) # 使用集群 rc.set('user:1001', 'John Doe') value = rc.get('user:1001') print(f'Value: {value}') # 使用哈希标签 rc.set('user:{1001}:profile', 'profile data') rc.set('user:{1001}:settings', 'settings data') # 批量操作(需要在同一槽位) pipe = rc.pipeline() pipe.set('user:{1001}:name', 'John') pipe.set('user:{1001}:age', '30') pipe.execute()
Spring Boot 配置:
# application.yml spring: redis: password: mypassword timeout: 2000ms cluster: nodes: - 192.168.1.101:7001 - 192.168.1.102:7002 - 192.168.1.103:7003 - 192.168.1.104:7004 - 192.168.1.105:7005 - 192.168.1.106:7006 max-redirects: 3 lettuce: pool: max-active: 100 max-idle: 10 min-idle: 5
🔄 集群扩容缩容
动态调整集群规模的操作流程:
集群扩容:
# 1. 启动新节点 redis-server /etc/redis/redis-7007.conf redis-server /etc/redis/redis-7008.conf # 2. 添加主节点 redis-cli --cluster add-node 192.168.1.107:7007 192.168.1.101:7001 -a mypassword # 3. 重新分片(分配槽位给新节点) redis-cli --cluster reshard 192.168.1.101:7001 -a mypassword # 按提示输入: # - 要移动的槽位数量 # - 目标节点ID(新节点) # - 源节点ID(all 表示从所有节点平均分配) # 4. 添加从节点 redis-cli --cluster add-node 192.168.1.108:7008 192.168.1.101:7001 --cluster-slave --cluster-master-id-a mypassword
集群缩容:
# 1. 迁移槽位(将要删除节点的槽位迁移到其他节点) redis-cli --cluster reshard 192.168.1.101:7001 -a mypassword # 将所有槽位从目标节点迁移出去 # 2. 删除从节点 redis-cli --cluster del-node 192.168.1.108:7008-a mypassword # 3. 删除主节点(确保没有槽位) redis-cli --cluster del-node 192.168.1.107:7007 -a mypassword # 4. 停止节点服务 redis-cli -h 192.168.1.107 -p 7007 -a mypassword SHUTDOWN redis-cli -h 192.168.1.108 -p 7008 -a mypassword SHUTDOWN
⚠️ 扩容缩容注意事项:
- 数据迁移:槽位迁移过程中会有短暂的性能影响
- 客户端重定向:客户端需要支持 MOVED 和 ASK 重定向
- 操作顺序:严格按照先迁移槽位再删除节点的顺序
- 监控告警:扩容缩容期间加强监控
集群模式优缺点
方面 | 优点 | 缺点 |
---|---|---|
扩展性 | 水平扩展,支持动态扩容 | 扩容过程复杂,有性能影响 |
高可用性 | 自动故障转移,无单点故障 | 需要至少3个主节点 |
性能 | 读写分离,并行处理 | 跨槽操作性能较差 |
数据分布 | 自动分片,负载均衡 | 数据倾斜可能性 |
运维复杂度 | 自动化程度高 | 配置和维护复杂 |
🚨 故障排查
集群常见问题的诊断和解决方法:
常见问题:
⚠️ 集群状态异常:
现象:cluster_state:fail
排查:检查节点连通性、槽位分配、节点数量
# 检查集群状态 redis-cli -c -h 192.168.1.101 -p 7001 -a mypassword CLUSTER INFO # 检查节点状态 redis-cli -c -h 192.168.1.101 -p 7001 -a mypassword CLUSTER NODES
⚠️ 槽位未完全分配:
现象:部分槽位没有分配给任何节点
解决:手动分配缺失的槽位
# 检查槽位分配 redis-cli --cluster check 192.168.1.101:7001 -a mypassword # 修复槽位分配 redis-cli --cluster fix 192.168.1.101:7001 -a mypassword
⚠️ 数据迁移失败:
现象:槽位迁移过程中断或失败
解决:重置槽位状态,重新迁移
# 重置槽位状态 CLUSTER SETSLOTSTABLE # 重新分配槽位 redis-cli --cluster reshard 192.168.1.101:7001 -a mypassword
✅ 集群健康检查脚本:
#!/bin/bash # Redis 集群健康检查脚本 CLUSTER_NODES=("192.168.1.101:7001" "192.168.1.102:7002" "192.168.1.103:7003") PASSWORD="mypassword" echo "=== Redis 集群健康检查 ===" # 检查集群整体状态 echo "1. 检查集群状态" for node in "${CLUSTER_NODES[@]}"; do host=${node%:*} port=${node#*:} state=$(redis-cli -h $host -p $port -a $PASSWORD CLUSTER INFO 2>/dev/null | grep cluster_state | cut -d: -f2 | tr -d '\r') if [ "$state" = "ok" ]; then echo "✓ $node: 状态正常" else echo "✗ $node: 状态异常 ($state)" fi done # 检查槽位分配 echo "\n2. 检查槽位分配" host=${CLUSTER_NODES[0]%:*} port=${CLUSTER_NODES[0]#*:} slots_info=$(redis-cli -h $host -p $port -a $PASSWORD CLUSTER SLOTS 2>/dev/null) if [ $? -eq 0 ]; then echo "✓ 槽位分配正常" else echo "✗ 槽位分配异常" fi # 检查节点连通性 echo "\n3. 检查节点连通性" for node in "${CLUSTER_NODES[@]}"; do host=${node%:*} port=${node#*:} if redis-cli -h $host -p $port -a $PASSWORD ping > /dev/null 2>&1; then echo "✓ $node: 连接正常" else echo "✗ $node: 连接失败" fi done # 检查主从关系 echo "\n4. 检查主从关系" nodes_info=$(redis-cli -h $host -p $port -a $PASSWORD CLUSTER NODES 2>/dev/null) master_count=$(echo "$nodes_info" | grep -c "master") slave_count=$(echo "$nodes_info" | grep -c "slave") echo "主节点数量: $master_count" echo "从节点数量: $slave_count" if [ $master_count -ge 3 ]; then echo "✓ 主节点数量充足" else echo "✗ 主节点数量不足" fi echo "\n=== 检查完成 ==="
💡 最佳实践:
- 节点规划:至少3个主节点,每个主节点配置1个从节点
- 硬件配置:节点分布在不同的物理机器上
- 网络优化:确保节点间网络延迟低且稳定
- 监控告警:监控集群状态、槽位分配、节点健康度
- 数据设计:合理使用哈希标签,避免热点数据
- 客户端配置:客户端支持集群模式和重定向
- 备份策略:定期备份集群数据
- 扩容规划:提前规划扩容策略和时机