灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

  • Redis

    • Redis 运维知识地图:从单机到 Cluster 排障
    • Redis 常用查询操作:五种数据类型速查 + 生产环境避坑
    • Redis 集群部署
    • Redis 大 key 分析:三条排查路径怎么选
    • Redis手动进行主从切换
    • Redis集群添加节点之后数据重新均匀分配
      • Slot 数据迁移到新节点(一定要在业务低峰期操作)
      • 为什么 reshard 必须业务低峰期执行
      • 验证迁移完成
        • 1. 集群状态检查
        • 2. 槽位分布确认
        • 3. 数据写入验证
        • 4. 节点负载均衡确认
      • 坑与边界
    • Redis槽位slot解读
    • Redis Cluster 新增节点 slot 迁移卡住:"open slots" 故障复盘
    • Redis集群的创建、剔除节点与新增节点操作过程
    • Redis配置文件解读
    • redis cluster压测
    • Redis 慢查询告警与抓包分析排障脚本
    • Redis 的可用内存过高时的自动驱逐 key 策略详解
  • 高性能KV

  • TiDB

  • Elasticsearch

  • 数据管道

  • 其他数据库

  • 数据库
  • Redis
灯下哥谭
2022-03-17
目录

Redis集群添加节点之后数据重新均匀分配

# redis 5 cluster集群增加节点动态扩容的步骤

已有集群为6个节点3组主从节点

版本说明

本文写于 2022-03。
redis-cli --cluster reshard/rebalance 的用法与 slot 迁移机制未变,经 2026-07 复核仍适用。

<PUBLIC_IP>:9002
<PUBLIC_IP>:9003
<PUBLIC_IP>:9002
<PUBLIC_IP>:9003
<PUBLIC_IP>:9001
<PUBLIC_IP>:9001
1
2
3
4
5
6

现要增加第4组主从节点

<PUBLIC_IP>:9005
<PUBLIC_IP>:9004
1
2

新增节点

  1. 创建一个新的主节点<PUBLIC_IP>:9004 命令里需要指定一个已有节点以便于获取集群信息,(新增节点在前,已有节点在后)该命令添加的节点默认一个主节点,但是没有分配slot,也没有key.

    /data/redis/redis9001/bin/redis-cli -h <PUBLIC_IP> -p 9001 -a <PASSWORD> --cluster add-node <PUBLIC_IP>:9004 <PUBLIC_IP>:9001
    
    1
  2. 将<PUBLIC_IP>:9005创建为<PUBLIC_IP>:9004的从节点。因为,集群会自动为从节点数最少的主节点创建从节点,所以这里不需要指定主节点。

    /data/redis/redis9001/bin/redis-cli -a '<PASSWORD>' --cluster add-node <PUBLIC_IP>:9005  <PUBLIC_IP>:9001 --cluster-slave
    
    1

# Slot 数据迁移到新节点(一定要在业务低峰期操作)

三个主节点的slot分布:主节点1: [0-5460],主节点2: [5461-10922],主节点3: [10923-16383]

预计分配之后的slot: 主节点1[0-4095],节点2:[6144-10922] 节点3:[12288-16383],主节点4:[4096-6144] +[10922-12288]

   <PUBLIC_IP>:9002 slots:[0-5460] (5461 slots) master
   <PUBLIC_IP>:9001  slots:[5461-10922] (5462 slots) master
   <PUBLIC_IP>:9001 slots:[10923-16383] (5461 slots) master
1
2
3

快速迁移 cluster reshard(业务繁忙时不建议使用)

# 指定迁移1365个slot迁移到新节点
/data/redis/redis9001/bin/redis-cli -a <PASSWORD> --cluster reshard <PUBLIC_IP>:9001 --cluster-from 6f7c25fc85f5e66484ea6679d5220dcaed5f3672  --cluster-to 26319656b03d9238839187ea2a074cf54e239ecf --cluster-slots 1365 --cluster-yes
1
2
#!/bin/sh
# Writed by yijian on 2020/8/10
# 迁移 slot 工具,多个slot迁移
#
# 使用时,需要指定如下几个参数:
# 1)参数1:必选参数,用于指定源节点(格式为:ip:port)
# 2)参数2:必选参数,用于指定目标节点(格式为:ip:port)
# 3)参数3:可选参数,用于指定访问 redis 的密码
# 4)参数4:开始slot
# 5)参数5:结束slot
#
# 使用示例(将2020从<PUBLIC_IP>:1383迁移到<PUBLIC_IP>:1386):
# move_redis_slot.sh 2020 <PUBLIC_IP>:1383 <PUBLIC_IP>:1386
#
# 执行本脚本时,有两个“确认”,
# 第一个“确认”是提示参数是否正确,
# 第二个“确认”是提示是否迁移已有的keys,
# 如果输入非yes则只迁移slot,不迁移已有keys。

# 确保redis-cli可用
REDIS_CLI=${REDIS_CLI:-redis-cli}
#REDIS_CLI="/usr/local/bin/redis-cli"
which "$REDIS_CLI" >/dev/null 2>&1
if test $? -ne 0; then
  echo "\`redis-cli\` not exists or not executable"
  exit 1
fi

# 参数检查
if test $# -ne 4 -a $# -ne 5; then
  echo -e "Usage: $(basename $0)  source_node destition_node redis_password \033[1;33mbegin end\033[m"
  echo -e "Example1: $(basename $0)  <PUBLIC_IP>:6379 <PUBLIC_IP>:6380 \033[1;33m450 460\033[m"
  echo -e "Example2: $(basename $0)  <PUBLIC_IP>:6379 <PUBLIC_IP>:6380 YourPassword \033[1;33m450 460\033[m"
  exit 1
fi

# SLOT=$1
SRC_NODE="$1"
DEST_NODE="$2"
REDIS_PASSOWRD="$3"
BEGIN_SLOT="$4"
END_SLOT="$5"
# 得到指定节点的 nodeid
function get_node_id() {
  node="$1"
  node_ip="$(echo $node | cut -d':' -f1)"
  node_port=$(echo $node | cut -d':' -f2)

  # 得到对应的 nodeid
  $REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
    -h $node_ip -p $node_port \
    CLUSTER NODES | awk -v node=$node -F'[ @]' '{if ($2==node) printf("%s",$1);}'
}

SRC_NODE_ID="$(get_node_id $SRC_NODE)"
SRC_NODE_IP="$(echo $SRC_NODE | cut -d':' -f1)"
SRC_NODE_PORT=$(echo $SRC_NODE | cut -d':' -f2)
DEST_NODE_ID="$(get_node_id $DEST_NODE)"
DEST_NODE_IP="$(echo $DEST_NODE | cut -d':' -f1)"
DEST_NODE_PORT=$(echo $DEST_NODE | cut -d':' -f2)

echo -e "\033[1;33mSource\033[m node: $SRC_NODE_IP:$SRC_NODE_PORT($SRC_NODE_ID)"
echo -e "\033[1;33mDestition\033[m node: $DEST_NODE_IP:$DEST_NODE_PORT($DEST_NODE_ID)"
if test -z "$SRC_NODE_ID"; then
  echo -e "Can not get the source node ID of \033[1;33m$SRC_NODE\033[m"
  exit 1
fi
if test -z "$DEST_NODE_ID"; then
  echo -e "Can not get the destition node ID of \033[1;33m$DEST_NODE\033[m"
  exit 1
fi

echo -en "Confirm to continue? [\033[1;33myes\033[m/\033[1;33mno\033[m]"
read -r -p " " input
if test "$input" != "yes"; then
  exit 1
fi
echo "........."

# 目标节点上执行 IMPORTING 操作
# 如果 $SLOT 已在目标节点,则执行时报错“ERR I'm already the owner of hash slot 1987”
echo -e "\033[1;33mImporting\033[m $BEGIN_SLOT $END_SLOT from $SRC_NODE($SRC_NODE_ID) to $DEST_NODE($DEST_NODE_ID) ..."
for ((SLOT = ${BEGIN_SLOT}; SLOT <= ${END_SLOT}; SLOT++)); do
  err=$($REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
    -h $DEST_NODE_IP -p $DEST_NODE_PORT \
    CLUSTER SETSLOT $SLOT IMPORTING $SRC_NODE_ID)
  echo "${SLOT}"
  if test "X$err" != "XOK"; then
    echo "[destition://$DEST_NODE_IP:$DEST_NODE_PORT] $err"
    exit 1
  fi
done

# 源节点上执行 MIGRATING 操作
# 如果 $SLOT 并不在源节点上,则执行时报错“ERR I'm not the owner of hash slot 1987”
echo -e "\033[1;33mMigrating\033[m $BEGIN_SLOT $END_SLOT from $SRC_NODE($SRC_NODE_ID) to $DEST_NODE($DEST_NODE_ID) ..."
for ((SLOT = ${BEGIN_SLOT}; SLOT <= ${END_SLOT}; SLOT++)); do
  err=$($REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
    -h $SRC_NODE_IP -p $SRC_NODE_PORT \
    CLUSTER SETSLOT $SLOT MIGRATING $DEST_NODE_ID)
  echo "${SLOT}"
  if test "X$err" != "XOK"; then
    echo "[source://$SRC_NODE_IP:$SRC_NODE_PORT] $err"
    exit 1
  fi
done

# 是否迁移已有的keys?
echo -en "Migrate keys in slot://$BEGIN_SLOT $END_SLOT? [\033[1;33myes\033[m/\033[1;33mno\033[m]"
read -r -p " " input
if test "$input" = "yes"; then
  first=1          # 是否第一轮keys迁移操作
  batch=1000       # 一次批量迁移的keys数
  timeout_ms=60000 # 超时时长(单位:毫秒)
  destination_db=0 # 对于redis集群,取值总是为0
  num_keys=0

  echo "........."
  echo -e "Migrating keys in slot://$BEGIN_SLOT $END_SLOT ..."
  while true; do
    # 在源节点上执行:
    # 借助命令“CLUSTER GETKEYSINSLOT”和命令“MIGRATE”迁移已有的keys
    for ((SLOT = ${BEGIN_SLOT}; SLOT <= ${END_SLOT}; SLOT++)); do
      keys="$($REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
        -h $SRC_NODE_IP -p $SRC_NODE_PORT \
        CLUSTER GETKEYSINSLOT $SLOT $batch | tr '\n' ' ' | xargs)"
      if test -z "$keys"; then
        if test $first -eq 1; then
          echo -e "No any keys to migrate in slot://$SLOT"
        else
          echo -e "Finished migrating all keys (\033[1;33m$num_keys\033[m) in slot://$SLOT"
        fi
        break 2
      fi
      
      first=0
      n=$(echo "$keys" | tr -cd ' ' | wc -c)
      num_keys=$(($num_keys + $n))
      #echo -e "(\033[1;33m$n\033[m)$keys"

      # 在源节点上执行命令“MIGRATE”迁移到目标节点
      # MIGRATE returns OK on success,
      # or NOKEY if no keys were found in the source instance
      if test -z "$REDIS_PASSOWRD"; then
        err=$($REDIS_CLI --raw \
          -h $SRC_NODE_IP -p $SRC_NODE_PORT \
          MIGRATE $DEST_NODE_IP $DEST_NODE_PORT "" $destination_db $timeout_ms \
          REPLACE KEYS $keys)
      else
        err=$($REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
          -h $SRC_NODE_IP -p $SRC_NODE_PORT \
          MIGRATE $DEST_NODE_IP $DEST_NODE_PORT "" $destination_db $timeout_ms \
          REPLACE AUTH "$REDIS_PASSOWRD" KEYS $keys)
      fi
      if test "X$err" = "XNOKEY"; then
        break
      fi
      echo -e "\033[1;33m$n\033[m keys migratd"
  
    done

  done
fi

# 取得所有 master 节点
nodes=($($REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" -h $DEST_NODE_IP -p $DEST_NODE_PORT \
  CLUSTER NODES | awk '{if (match($3,"master")) printf("%s\n",$2);}'))

# 在所有 master 节点上执行 NODE 操作
# 实际上,只可对源节点和目标节点执行 NODE 操作,
# 新的配置会自动在集群中传播。
echo "........."
for node in ${nodes[*]}; do
  node_ip="$(echo $node | cut -d':' -f1)"
  node_port=$(echo $node | cut -d':' -f2)
  echo -en "NODE: $node_ip:$node_port "
  for ((SLOT = ${BEGIN_SLOT}; SLOT <= ${END_SLOT}; SLOT++)); do
    err=$($REDIS_CLI --raw --no-auth-warning -a "$REDIS_PASSOWRD" \
      -h $node_ip -p $node_port \
      CLUSTER SETSLOT $SLOT NODE $DEST_NODE_ID)
    echo -e "\033[1;33m$err\033[m"
  done
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#从<PUBLIC_IP>:9002迁移[4095到5460共1365]个slots到新的主节点<PUBLIC_IP>:9004
#最好多次少量迁移 [4095 4595],[4596 5095],[5096 5460]
sudo sh /tmp/redis_slot_move_more.sh <PUBLIC_IP>:9002 <PUBLIC_IP>:9004 YourPassword 4096 5460 
#从<PUBLIC_IP>:9001迁移[5461到6826共1365]个slots到新的主节点<PUBLIC_IP>:9004
sudo sh /tmp/redis_slot_move_more.sh <PUBLIC_IP>:9001 <PUBLIC_IP>:9004 YourPassword 5461 6144 
#从<PUBLIC_IP>:9001迁移[10923到12288共1365]个slots到新的主节点<PUBLIC_IP>:9004
sudo sh /tmp/redis_slot_move_more.sh <PUBLIC_IP>:9001 <PUBLIC_IP>:9004 YourPassword 10922 12288
1
2
3
4
5
6
7

若出现警告,则手动执行即可 cluster setslot 11763 stable

Check for open slots...
[WARNING] Node <PUBLIC_IP>:8001 has slots in importing state 11763.
[WARNING] The following slots are open: 11763.
#解决:
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
<PUBLIC_IP>:8001> cluster setslot 11763 stable
OK
再执行 check
1
2
3
4
5
6
7
8

# 为什么 reshard 必须业务低峰期执行

Redis Cluster 的槽位迁移是渐进式的,不是原子操作。迁移单个槽位的过程:

  1. 源节点标记槽位为 MIGRATING
  2. 目标节点标记槽位为 IMPORTING
  3. 客户端对该槽位 key 的访问会触发 ASK 重定向
  4. 实际 key 被逐个搬移
  5. 完成后,槽位归属正式切换到目标节点

业务高峰期的风险:

  • ASK 重定向风暴:大量客户端收到重定向,增加网络往返和延迟
  • 大 key 阻塞:单个 key 过大时,MIGRATE 命令会阻塞源节点
  • 迁移中断:网络抖动可能导致槽位卡在 importing/migrating 中间态(即「open slots」故障)

建议:在业务低峰期执行,并分批迁移(每次 100-500 个槽位),而非一次性迁移 4000+。


# 验证迁移完成

# 1. 集群状态检查

redis-cli --cluster check <any-node>:6379
1

期望:

  • [OK] All 16384 slots covered
  • 无 [WARNING] Node ... has slots in importing/migrating state

# 2. 槽位分布确认

redis-cli -h <new-master> -p 6379 cluster info
1

检查:

  • cluster_state:ok
  • cluster_slots_assigned:16384
  • cluster_slots_ok:16384

# 3. 数据写入验证

# 向新节点写入测试数据
redis-cli -c -h <new-master> -p 6379 set test:key:on:new:node "test_value"

# 验证能读回(可能路由到其他节点,-c 会自动跟随)
redis-cli -c -h <any-node> -p 6379 get test:key:on:new:node
1
2
3
4
5

# 4. 节点负载均衡确认

redis-cli --cluster info <any-node>:6379
1

检查每个主节点的 key 数量和内存占用是否大致均衡。


# 坑与边界

  1. 迁移中断后的 open slots:如果迁移过程中脚本/网络中断,槽位可能卡在 importing 或 migrating 状态。解决:

    # 确认槽位是否已搬空
    redis-cli -h <target-node> cluster countkeysinslot <slot-id>
    
    # 如果返回 0,直接置为 stable
    redis-cli -h <target-node> cluster setslot <slot-id> stable
    
    1
    2
    3
    4
    5

    详细处理见《Redis Cluster 新增节点 slot 迁移卡住故障复盘》。

  2. 迁移期间的多 key 操作原子性:Redis Cluster 不支持跨槽位的多 key 事务(MULTI/EXEC)。迁移期间,即使 key 原本在同一槽位,如果恰好在迁移瞬间,事务可能失败。避免在迁移期间执行跨 key 事务。

  3. 新节点默认无槽位:add-node 只把节点加入集群拓扑,不会自动分配槽位。忘记 reshard 会导致新主节点成为「空主」——集群状态正常,但它不处理任何请求。

  4. 从节点的 slot 分布不显示:cluster nodes 只显示主节点的槽位范围,从节点显示为空。这是设计如此——从节点复用主节点的槽位信息。

  5. reshard 期间的读写性能:迁移大量槽位时,源节点的 MIGRATE 操作会消耗 CPU 和网络带宽,可能导致正常请求的延迟上升。监控 instantaneous_ops_per_sec 和 latency 指标。

  6. batch 迁移脚本的风险:本文附带的批量迁移脚本使用循环逐个处理槽位,效率较低但可控。极端场景下(单槽位包含数百万 key),建议改用 redis-cli --cluster reshard 的交互式模式,可以控制每次迁移的 key 数量和超时。

#集群运维#Redis
上次更新: 9/11/2026

← Redis手动进行主从切换 Redis槽位slot解读→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式