Redis集群扩容数据迁移方案分析

来源:这里教程网 时间:2026-02-25 10:11:16 作者:
一、迁移前准备1.1 环境检查1.2 新节点准备1.3 业务准备二、详细迁移步骤2.1 添加新节点到集群2.2 计算迁移计划2.3 执行迁移操作方案A:自动重新分片(推荐)方案B:手动控制迁移(更精确)2.4 迁移过程监控2.5 迁移后验证三、迁移优化和注意事项3.1 性能优化参数3.2 大key特殊处理3.3 回滚方案3.4 客户端配置更新3.5 平滑迁移策略:3.6. 最佳实践建议3.7 注意事项四、完整迁移脚本五、监控和告警脚本六、 Redis Cluster 为什么只有 16384 个槽位?技术原因 - 心跳包大小限制性能与扩展性的平衡Redis 作者的解释为什么不能修改这个数字?为什么不需要修改这个数字?七、槽位分配示例3 个节点的分配(原本):增加到 4 个节点后:槽位计算公式实践建议

问题:一个Redis集群如果从原先的3个节点扩容为4个节点,存量数据是否需要迁移?如果需要该怎么迁移?

首先对于数据是否需要迁移答案是肯定的:需要迁移,但只需迁移部分数据(约 25%的数据)。

原因:从 3 个节点扩到 4 个节点,需要重新分配数据分布,使每个节点承担约 25%的数据量,而不是原来的 33%。以下是具体的数据迁移方案:

一、迁移前准备

1.1 环境检查

# 1. 检查当前集群状态 redis-cli --cluster check 192.168.1.101:6379 # 2. 查看槽分布情况 redis-cli -h 192.168.1.101 -p 6379 cluster slots # 或使用更详细的查看方式 echo "cluster slots" | redis-cli -h 192.168.1.101 -p 6379 | python -m json.tool # 3. 检查数据量预估 for node in 192.168.1.{101,102,103}:6379; do echo "=== $node ===" redis-cli -c -h ${node%:*} -p ${node#*:} dbsize redis-cli -c -h ${node%:*} -p ${node#*:} info memory | grep used_memory_human done # 4. 备份当前集群配置 redis-cli -h 192.168.1.101 -p 6379 cluster nodes > cluster_nodes_backup_$(date +%Y%m%d).txt redis-cli -h 192.168.1.101 -p 6379 cluster info > cluster_info_backup_$(date +%Y%m%d).txt

1.2 新节点准备

# 1. 新节点配置文件(redis-4.conf) port 6379 cluster-enabled yes cluster-config-file nodes-4.conf cluster-node-timeout 15000 cluster-require-full-coverage yes appendonly yes daemonize yes logfile "/var/log/redis/redis-4.log" dir /var/lib/redis/4/ maxmemory 8gb maxmemory-policy volatile-lru # 2. 创建数据目录和日志目录 mkdir -p /var/lib/redis/4/ mkdir -p /var/log/redis/ chown -R redis:redis /var/lib/redis/4/ /var/log/redis/ # 3. 启动新节点 redis-server /etc/redis/redis-4.conf # 4. 验证新节点启动 redis-cli -h 192.168.1.104 -p 6379 ping

1.3 业务准备

通知业务方:提前通知相关业务团队迁移时间窗口

设置维护窗口:建议在业务低峰期进行(如凌晨2:00-5:00)

客户端检查

确保客户端使用集群模式连接检查客户端重试机制是否完善验证客户端是否支持MOVEDASK重定向

二、详细迁移步骤

2.1 添加新节点到集群

如果你的集群数据量不大(<10GB),可以直接将新节点作为主节点加入集群。对于大型Redis集群,先添加为从节点再提升为主节点是更优的扩容策略。(具体可看后面的完整迁移脚本)

# 1. 将新节点作为主节点加入集群 redis-cli --cluster add-node 192.168.1.104:6379 192.168.1.101:6379 # 2. 验证节点已加入但未分配槽 redis-cli -h 192.168.1.101 -p 6379 cluster nodes | grep 192.168.1.104 # 输出应显示新节点,但flags为"master"且没有槽分配 # 3. 确认当前槽分布(迁移前) echo "当前槽分布:" redis-cli --cluster info 192.168.1.101:6379

2.2 计算迁移计划

#!/usr/bin/env python3 # calculate_slots_redistribution.py import sys TOTAL_SLOTS = 16384 CURRENT_NODES = 3 NEW_NODES = 4 # 理想分布 slots_per_node = TOTAL_SLOTS // NEW_NODES # 4096 print("=" * 60) print("Redis集群槽迁移计算") print("=" * 60) print(f"总槽数: {TOTAL_SLOTS}") print(f"当前节点数: {CURRENT_NODES}") print(f"目标节点数: {NEW_NODES}") print(f"目标每个节点槽数: {slots_per_node}") print() # 从每个现有节点需要迁移出的槽数 slots_to_move_from_each = (TOTAL_SLOTS // CURRENT_NODES) - slots_per_node print(f"从每个现有节点需要迁移出槽数: {slots_to_move_from_each}") print() # 实际分配方案 print("建议迁移方案:") print(f"1. 从节点1迁移 {slots_to_move_from_each} 个槽到新节点") print(f"2. 从节点2迁移 {slots_to_move_from_each} 个槽到新节点") print(f"3. 从节点3迁移 {slots_to_move_from_each} 个槽到新节点") print() print(f"迁移后分布:") print(f"• 节点1: {slots_per_node} 槽") print(f"• 节点2: {slots_per_node} 槽") print(f"• 节点3: {slots_per_node} 槽") print(f"• 节点4: {slots_per_node} 槽") print("=" * 60)

2.3 执行迁移操作

方案A:自动重新分片(推荐)

#!/bin/bash # auto_reshard.sh CLUSTER_NODE="192.168.1.101:6379" NEW_NODE_ID="" # 需要先获取新节点的ID # 1. 获取新节点ID NEW_NODE_ID=$(redis-cli -h 192.168.1.104 -p 6379 cluster myid | tr -d '"') echo "新节点ID: $NEW_NODE_ID" # 2. 执行自动重新平衡 # --cluster-use-empty-masters: 使用空的主节点 # --cluster-threshold: 平衡阈值,默认2表示差异超过2%时触发平衡 # --cluster-timeout: 迁移超时时间(毫秒) redis-cli --cluster rebalance $CLUSTER_NODE \ --cluster-weight ${NEW_NODE_ID}=1 \ --cluster-use-empty-masters \ --cluster-threshold 1 \ --cluster-timeout 60000

方案B:手动控制迁移(更精确)

#!/bin/bash # manual_reshard.sh SOURCE_NODE1="192.168.1.101:6379" SOURCE_NODE2="192.168.1.102:6379" SOURCE_NODE3="192.168.1.103:6379" NEW_NODE="192.168.1.104:6379" SLOTS_PER_MOVE=100 # 每次迁移100个槽,避免一次性迁移过多 # 1. 获取所有节点ID NODE1_ID=$(redis-cli -h 192.168.1.101 -p 6379 cluster myid | tr -d '"') NODE2_ID=$(redis-cli -h 192.168.1.102 -p 6379 cluster myid | tr -d '"') NODE3_ID=$(redis-cli -h 192.168.1.103 -p 6379 cluster myid | tr -d '"') NEW_NODE_ID=$(redis-cli -h 192.168.1.104 -p 6379 cluster myid | tr -d '"') echo "节点ID列表:" echo "- 节点1: $NODE1_ID" echo("- 节点2: $NODE2_ID") echo("- 节点3: $NODE3_ID") echo("- 新节点: $NEW_NODE_ID") # 2. 从每个源节点迁移槽到新节点 for source in "$NODE1_ID" "$NODE2_ID" "$NODE3_ID"; do echo "从节点 $source 迁移槽到新节点..." # 交互式迁移,每次迁移1365个槽(根据计算得出) redis-cli --cluster reshard $SOURCE_NODE1 \ --cluster-from $source \ --cluster-to $NEW_NODE_ID \ --cluster-slots 1365 \ --cluster-yes \ --cluster-timeout 30000 \ --cluster-pipeline 10 # 每次迁移10个key # 等待10秒,让集群稳定 sleep 10 # 检查迁移进度 redis-cli --cluster check $SOURCE_NODE1 | grep -A5 "Slot distribution" done # 3. 验证最终槽分布 echo "最终槽分布验证:" redis-cli --cluster check $SOURCE_NODE1

2.4 迁移过程监控

#!/bin/bash # migration_monitor.sh CLUSTER_NODE="192.168.1.101:6379" LOG_FILE="migration_monitor_$(date +%Y%m%d_%H%M%S).log" monitor_migration() { while true; do echo "========================================" >> $LOG_FILE echo "时间: $(date)" >> $LOG_FILE echo "========================================" >> $LOG_FILE # 1. 检查集群健康状态 echo "[集群状态]" >> $LOG_FILE redis-cli --cluster check $CLUSTER_NODE 2>&1 | tail -20 >> $LOG_FILE # 2. 检查槽迁移状态 echo -e "\n[槽状态]" >> $LOG_FILE redis-cli -h 192.168.1.101 -p 6379 cluster slots | \ awk '{print "节点区间:", $1"-"$2, "数量:", $2-$1+1}' | \ sort -n >> $LOG_FILE # 3. 检查各节点内存使用 echo -e "\n[内存使用]" >> $LOG_FILE for node in 192.168.1.{101,102,103,104}:6379; do mem=$(redis-cli -c -h ${node%:*} -p ${node#*:} info memory | \ grep "used_memory_human" | cut -d: -f2) echo "$node: $mem" >> $LOG_FILE done # 4. 检查迁移中的key数量 echo -e "\n[各节点键数量]" >> $LOG_FILE for node in 192.168.1.{101,102,103,104}:6379; do count=$(redis-cli -c -h ${node%:*} -p ${node#*:} dbsize) echo "$node: $count keys" >> $LOG_FILE done # 5. 检查连接数 echo -e "\n[连接数]" >> $LOG_FILE for node in 192.168.1.{101,102,103,104}:6379; do conn=$(redis-cli -c -h ${node%:*} -p ${node#*:} info clients | \ grep "connected_clients" | cut -d: -f2) echo "$node: $conn connections" >> $LOG_FILE done # 等待30秒后再次检查 sleep 30 # 清屏并显示最新状态 clear tail -50 $LOG_FILE done } # 启动监控 monitor_migration

2.5 迁移后验证

#!/bin/bash # post_migration_validation.sh echo "=========== 迁移后验证 ===========" # 1. 验证集群状态 echo "1. 集群状态检查..." redis-cli --cluster check 192.168.1.101:6379 if [ $? -eq 0 ]; then echo "✓ 集群状态正常" else echo "✗ 集群状态异常" exit 1 fi # 2. 验证槽分布 echo -e "\n2. 槽分布验证..." TOTAL_SLOTS=$(redis-cli -h 192.168.1.101 -p 6379 cluster info | grep "cluster_slots_assigned" | cut -d: -f2) if [ "$TOTAL_SLOTS" -eq 16384 ]; then echo "✓ 所有槽已分配 (16384)" else echo "✗ 槽分配不完整: $TOTAL_SLOTS" exit 1 fi # 3. 验证槽是否均匀分布 echo -e "\n3. 槽分布均匀性检查..." SLOT_DISTRIBUTION=$(redis-cli --cluster info 192.168.1.101:6379 | grep -A4 "Slot distribution") echo "$SLOT_DISTRIBUTION" # 4. 数据抽样验证 echo -e "\n4. 数据抽样验证..." # 随机测试一些key是否可访问 TEST_KEYS=("user:1001" "session:abc123" "product:500" "order:20230101") for key in "${TEST_KEYS[@]}"; do # 先设置测试key(如果不存在) redis-cli -c -h 192.168.1.101 -p 6379 set "$key" "test_value_$(date +%s)" > /dev/null 2>&1 # 获取key值 result=$(redis-cli -c -h 192.168.1.101 -p 6379 get "$key" 2>/dev/null) if [ -n "$result" ]; then echo "✓ Key '$key' 访问正常" else echo "✗ Key '$key' 访问失败" fi done # 5. 性能测试 echo -e "\n5. 简单性能测试..." START_TIME=$(date +%s%N) for i in {1..100}; do redis-cli -c -h 192.168.1.101 -p 6379 set "perf_test:$i" "value_$i" > /dev/null 2>&1 done END_TIME=$(date +%s%N) DURATION=$((($END_TIME - $START_TIME)/1000000)) echo "100次SET操作耗时: ${DURATION}ms" # 6. 清理测试数据 echo -e "\n6. 清理测试数据..." for i in {1..100}; do redis-cli -c -h 192.168.1.101 -p 6379 del "perf_test:$i" > /dev/null 2>&1 done for key in "${TEST_KEYS[@]}"; do redis-cli -c -h 192.168.1.101 -p 6379 del "$key" > /dev/null 2>&1 done echo -e "\n=========== 验证完成 ==========="

三、迁移优化和注意事项

3.1 性能优化参数

# 在迁移命令中调整以下参数优化性能: redis-cli --cluster reshard <host>:<port> \ --cluster-slots <num> \ --cluster-pipeline 100 \ # 增大pipeline大小 --cluster-timeout 120000 \ # 增加超时时间 --cluster-replace \ # 允许替换已存在的key --cluster-yes # 自动确认

3.2 大key特殊处理

如果集群中有大key(> 1MB),需要特殊处理:

# 1. 查找大key redis-cli -h 192.168.1.101 -p 6379 --bigkeys # 2. 对大key所在槽单独迁移 # 使用较小的pipeline值 redis-cli --cluster reshard 192.168.1.101:6379 \ --cluster-from <source_node_id> \ --cluster-to <target_node_id> \ --cluster-slots <slot_number> \ --cluster-pipeline 1 \ # 对大key使用较小的pipeline --cluster-timeout 300000

3.3 回滚方案

如果迁移出现问题,需要回滚:

# 1. 停止新节点 redis-cli -h 192.168.1.104 -p 6379 shutdown # 2. 将槽迁移回原节点 # 需要记录迁移前的槽分布,然后反向迁移 # 3. 从集群中移除问题节点 redis-cli --cluster del-node 192.168.1.101:6379 <new_node_id> # 4. 恢复备份配置 # 如果有配置备份,恢复到之前的状态

3.4 客户端配置更新

迁移完成后,更新客户端配置:更新配置时采用滚动更新,避免全量重启

# Python客户端配置示例 import redis # 更新节点列表 cluster_nodes = [ {"host": "192.168.1.101", "port": 6379}, {"host": "192.168.1.102", "port": 6379}, {"host": "192.168.1.103", "port": 6379}, {"host": "192.168.1.104", "port": 6379}, # 新增节点 ] # 重新初始化集群连接 cluster_client = redis.RedisCluster( startup_nodes=cluster_nodes, decode_responses=True, socket_connect_timeout=5, retry_on_timeout=True, max_connections=50 )

3.5 平滑迁移策略:

    分批次迁移:按 key 前缀或槽位范围分批迁移监控迁移速度:控制迁移速度,避免影响线上性能数据校验:迁移后验证数据一致性回滚计划:准备回滚方案

3.6. 最佳实践建议

选择低峰期:在业务低峰时段执行迁移充分备份:迁移前备份所有节点数据监控指标:内存使用率、QPS 和延迟、网络带测试验证:在测试环境先演练完整流程

3.7 注意事项

数据一致性:迁移过程中确保数据不丢失客户端连接:客户端需要感知新的节点拓扑主从复制:如果有主从架构,需要考虑复制链路的调整持久化:迁移期间可能需要暂时关闭 AOF 重写等操作

四、完整迁移脚本

适合生产环境中,特别是对于大型Redis集群,先添加为从节点再提升为主节点,以利用数据预同步,减少迁移时间。

#!/bin/bash # complete_migration.sh # # Redis集群从3节点扩容到4节点完整迁移脚本 # 使用方法: ./complete_migration.sh <new_node_ip> set -e # 遇到错误立即退出 NEW_NODE="${1:-192.168.1.104}" PORT=6379 CLUSTER_ENTRY="192.168.1.101:6379" LOG_DIR="/var/log/redis_migration" mkdir -p $LOG_DIR log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_DIR/migration.log } check_prerequisites() { log "检查前置条件..." # 检查Redis版本 redis_version=$(redis-cli -v | awk '{print $2}' | cut -d. -f1) if [ "$redis_version" -lt 5 ]; then log "错误: Redis版本需要5.0以上" exit 1 fi # 检查集群状态 if ! redis-cli --cluster check $CLUSTER_ENTRY > /dev/null 2>&1; then log "错误: 集群状态不正常" exit 1 fi # 检查新节点是否可访问 if ! redis-cli -h $NEW_NODE -p $PORT ping > /dev/null 2>&1; then log "错误: 新节点无法访问" exit 1 fi log "✓ 前置条件检查通过" } add_new_node() { log "添加新节点到集群..." redis-cli --cluster add-node ${NEW_NODE}:${PORT} $CLUSTER_ENTRY \ --cluster-slave \ --cluster-master-id $(redis-cli -h $CLUSTER_ENTRY cluster nodes | grep master | head -1 | awk '{print $1}') \ 2>&1 | tee -a $LOG_DIR/add_node.log # 等待节点同步 sleep 10 # 将新节点提升为主节点 NEW_NODE_ID=$(redis-cli -h $NEW_NODE -p $PORT cluster myid | tr -d '"') redis-cli --cluster rebalance $CLUSTER_ENTRY \ --cluster-weight ${NEW_NODE_ID}=1 \ --cluster-use-empty-masters \ --cluster-threshold 1 \ 2>&1 | tee -a $LOG_DIR/promote_node.log log "✓ 新节点添加完成" } perform_migration() { log "开始数据迁移..." # 获取新节点ID NEW_NODE_ID=$(redis-cli -h $NEW_NODE -p $PORT cluster myid | tr -d '"') # 执行重新分片 redis-cli --cluster reshard $CLUSTER_ENTRY \ --cluster-from all \ --cluster-to $NEW_NODE_ID \ --cluster-slots 4096 \ --cluster-yes \ --cluster-timeout 120000 \ --cluster-pipeline 50 \ 2>&1 | tee -a $LOG_DIR/reshard.log log "✓ 数据迁移完成" } post_migration_checks() { log "执行迁移后检查..." # 检查集群状态 if redis-cli --cluster check $CLUSTER_ENTRY > $LOG_DIR/final_check.log 2>&1; then log "✓ 集群状态正常" else log "✗ 集群状态异常,请检查日志: $LOG_DIR/final_check.log" exit 1 fi # 验证槽分布 slots_assigned=$(redis-cli -h ${CLUSTER_ENTRY%:*} -p ${CLUSTER_ENTRY#*:} cluster info | \ grep "cluster_slots_assigned" | cut -d: -f2 | tr -d '\r') if [ "$slots_assigned" -eq 16384 ]; then log "✓ 所有槽已正确分配" else log "✗ 槽分配不完整: $slots_assigned/16384" exit 1 fi log "✓ 所有检查通过" } main() { log "开始Redis集群扩容迁移" log "目标: 从3节点扩容到4节点" log "新节点: $NEW_NODE:$PORT" check_prerequisites add_new_node perform_migration post_migration_checks log "迁移完成!" log "详细信息请查看: $LOG_DIR/" } main "$@"

五、监控和告警脚本

迁移期间设置监控:

# 监控关键指标 watch -n 5 ' echo "=== Redis集群迁移监控 ===" echo "时间: $(date)" echo echo "1. 集群状态:" redis-cli -h 192.168.1.101 -p 6379 cluster info | grep -E "(cluster_state|cluster_slots_ok|cluster_known_nodes)" echo echo "2. 迁移进度:" redis-cli --cluster info 192.168.1.101:6379 | grep -A2 "Slot distribution" echo echo "3. 节点状态:" for node in 101 102 103 104; do echo -n "192.168.1.$node: " redis-cli -h 192.168.1.$node -p 6379 ping 2>/dev/null && \ redis-cli -h 192.168.1.$node -p 6379 info memory | grep used_memory_human | cut -d: -f2 done '

这个详细方案提供了完整的迁移流程,包括准备、执行、验证和监控各个阶段的具体操作步骤和脚本,可以根据实际情况进行调整使用。

六、 Redis Cluster 为什么只有 16384 个槽位?

技术原因 - 心跳包大小限制

Redis 集群节点之间通过 CLUSTER MEET 消息通信,其中包含了节点负责的槽位信息。每个槽位用 1 个 bit 表示(0 或 1)。

如果槽位数太多

假设 65536 个槽位(2^16),心跳包需要 65536 / 8 = 8192 bytes = 8KB每个节点每秒钟向其他 N-1 个节点发送心跳10 个节点的集群:8KB * 10 * 2(收发)≈ 160KB/s 的网络开销

16384 个槽位

16384 / 8 = 2048 bytes = 2KB同样的 10 节点集群:2KB * 10 * 2 ≈ 40KB/s网络开销更合理

性能与扩展性的平衡

# 计算示例 65536 槽位:节点数太少时,每个节点槽位太多 → 数据倾斜 16384 槽位:更合理的分布,即使节点少也能均匀分布 # 实际最大节点数理论 16384 个槽位 / 最低建议每个节点 100 个槽位 ≈ 163 个节点 对于绝大多数场景,163 个节点已经足够。

Redis 作者的解释

Antirez(Redis 作者)在 GitHub issue 中解释:

16384 是 2^14,足够大以实现良好分布65536 会增加心跳包大小,网络开销较大CRC16 算法输出 16 位(65536),但 Redis 只使用 14 位(16384)

为什么不能修改这个数字?

兼容性:所有 Redis 客户端都硬编码了 16384协议固定:Redis 集群协议中槽位数量是固定的工具链依赖:所有集群管理工具都基于这个设计

为什么不需要修改这个数字?

16384 是 Redis Cluster 的硬编码限制,它是在性能、扩展性和实现复杂度之间的最佳平衡点。这个设计虽然限制了理论最大节点数,但足以满足绝大多数生产场景的需求。如果你的应用需要更多节点,可能需要考虑其他分片方案,如基于代理的分片(Codis)或客户端分片。

七、槽位分配示例

3 个节点的分配(原本):

Node1: slots 0-5460 Node2: slots 5461-10922 Node3: slots 10923-16383

增加到 4 个节点后:

Node1: slots 0-4095 # 迁移 1365 个槽位给 Node4 Node2: slots 4096-8191 # 迁移 1365 个槽位给 Node4 Node3: slots 8192-12287 # 迁移 1365 个槽位给 Node4 Node4: slots 12288-16383 # 获得 4096 个槽位

槽位计算公式

Redis 使用 CRC16 算法计算 key 属于哪个槽位:

def slot(key): # 1. 如果 key 包含 "{}",只计算括号内的部分 # 2. 计算 CRC16(key) mod 16384 return crc16(key_with_hash_tag) % 16384

实践建议

虽然槽位数量固定,但实际应用中:

每个节点建议最少 100 个槽位,保证数据分布均匀最大推荐节点数16384 / 100 ≈ 163 个主节点如果节点数 < 槽位数,Redis 会自动平均分配槽位数据倾斜时:可以手动调整槽位分布

到此这篇关于Redis集群扩容数据迁移方案分析的文章就介绍到这了,

相关推荐

热文推荐