1. 为什么需要 Redis Cluster
前面已经讲过主从复制和 Sentinel。主从复制解决数据副本问题,Sentinel 解决 master 故障后的自动切换问题。
但它们还有一个核心限制:一个 master 仍然要承载整个数据集。
当数据量、写入量、连接数继续增长时,单个 master 复制集很难一直扩下去。这时就要考虑 Redis Cluster,把数据分散到多个 master 上,每个 master 负责一部分数据。

Redis Cluster 的核心目标是多 master 分片,同时提供集群内部的节点级故障转移能力。
2. Cluster 能做什么
Redis Cluster 的核心能力:
| 能力 | 说明 |
|---|---|
| 数据分片 | 通过 hash slot 把 key 分散到多个 master |
| 水平扩容 | 新增 master 后迁移部分 slot |
| 高可用 | master 可以有 replica,master 故障后 replica 自动晋升 |
| 客户端路由 | 客户端连接任意节点后,根据重定向访问正确节点 |
Cluster 和 Sentinel 的区别:
| 对比项 | Sentinel | Cluster |
|---|---|---|
| 数据分片 | 不支持 | 支持 |
| master 数量 | 通常一个 master 复制集 | 多个 master |
| 故障转移 | Sentinel 负责 | Cluster 自身负责 |
| 客户端连接 | 通过 Sentinel 发现 master | 客户端需要支持 slot 路由和重定向 |
Cluster 自带故障转移能力,所以 Cluster 模式下通常不再额外搭配 Sentinel。可以理解为:Sentinel 负责普通主从复制组的 failover,而 Cluster 把分片和 failover 都内置到集群协议里。
3. Hash Slot 槽位
Redis Cluster 没有使用一致性哈希,而是引入了 hash slot 映射模型。
Redis Cluster 固定有 16384 个 slot。每个 key 会通过下面公式计算所属 slot:
HASH_SLOT = CRC16(key) mod 16384
每个 master 负责一部分 slot。客户端写入 key 时,先算出 key 属于哪个 slot,再路由到负责该 slot 的 master。
slot 数量是全局固定的,不会因为节点数量变化而变化。扩容或缩容时改变的是 slot 到 master 的归属关系,而不是重新定义 slot 总数。
Redis 的 slot 计算使用 CRC16,并通过 & 0x3FFF 得到 0-16383 范围内的槽位。日常理解为 CRC16(key) % 16384 即可。

查看 key 的 slot:
cluster keyslot user:1001
客户端命令:
redis-cli -c -p 6381 -a 111111
这里 -c 表示启用 Cluster 模式,redis-cli 会根据 MOVED 或 ASK 重定向自动跳转到正确节点。
注意,-c 只是 redis-cli 的 Cluster 模式。生产应用依赖的是客户端驱动的 slot cache、MOVED / ASK 处理和拓扑刷新能力。
4. 三种分片思路对比
4.1 哈希取余
最简单的方式是:
server = hash(key) % N
优点是简单,缺点是节点数量变化时,大量 key 的映射都会变化。
4.2 一致性哈希
一致性哈希通过哈希环减少节点增减时的数据迁移量。它比简单取余更适合动态增减节点,但节点较少时可能出现数据倾斜,所以通常还要引入虚拟节点。
4.3 Hash Slot
Redis Cluster 在 key 和节点之间加了一层 slot。
key 先映射到 slot,slot 再分配给 master。扩容或缩容时,迁移的是 slot,而不是直接重算所有 key 到节点的映射。
4.4 哈希槽计算示例
Redis 集群中内置了 16384 个哈希槽。Redis 会根据节点数量,尽量均等地将哈希槽映射到不同的节点上。
当需要在 Redis 集群中存放一个 key-value 时,Redis 会先对 key 使用 CRC16 算法计算出一个结果,然后再用这个结果对 16384 求余:
CRC16(key) % 16384
这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是会被映射到某个节点上。
例如:
key A 在 Node2 上
key C 在 Node3 上
4.5 哈希槽分配示意
Redis Cluster 总共有:
0-16383
这 16384 个哈希槽。
如果有 3 个 Redis 节点,可以将哈希槽大致分成 3 段:
0-5460
5461-10922
10923-16383
对应关系可以理解为:
Node1:0-5460 存储 5461 个 hash 槽
Node2:5461-10922 存储 5462 个 hash 槽
Node3:10923-16383 存储 5461 个 hash 槽
4.6 Java 代码示例
@Test
public void test3() {
// import io.lettuce.core.cluster.SlotHash;
System.out.println(SlotHash.getSlot("A")); // 6373
System.out.println(SlotHash.getSlot("B")); // 10374
System.out.println(SlotHash.getSlot("C")); // 14503
System.out.println(SlotHash.getSlot("hello")); // 866
}
5. 为什么是 16384 个 slot
经典面试题:CRC16 可以产生 65536 个值,为什么 Redis Cluster 不使用 65536 个 slot,而是 16384 个?
常见解释有三点:
- Cluster 节点之间的心跳消息会携带 slot bitmap。16384 个 slot 的 bitmap 大约 2KB,65536 个 slot 的 bitmap 大约 8KB,后者心跳包更大。
- Redis Cluster 官方并不建议节点数量无限增大,16384 个 slot 对常见规模集群已经够用。
- slot 数太多,在节点较少时 bitmap 压缩效果反而可能变差,带来额外网络开销。
所以 16384 是一个工程折中:够用、开销低、迁移粒度也比较合适。
6. Hash Tag
Cluster 下多 key 命令有一个限制:多个 key 必须属于同一个 slot。
例如:
mset user:1:name zhangsan user:1:age 18
如果两个 key 不在同一个 slot,会报类似错误:
CROSSSLOT Keys in request don't hash to the same slot
如果确实需要让多个 key 落到同一个 slot,可以使用 hash tag,也就是 {} 中的内容参与 slot 计算。
示例:
set user:{1001}:name zhangsan
set user:{1001}:age 18
cluster keyslot user:{1001}:name
cluster keyslot user:{1001}:age
这两个 key 的 hash tag 都是 1001,所以会落到同一个 slot。
Cluster 中 pipeline 也要注意 slot 限制。跨 slot pipeline 不能被当成单机 Redis 的批量写入来使用,生产上应尽量把同一批 pipeline 命令控制在同一个 slot,或者让客户端按 slot 拆分。
7. 3 主 3 从集群规划
实验环境使用 3 台机器,每台机器 2 个 Redis 实例:
| 机器 | 端口 |
|---|---|
| 192.168.10.101 | 6381、6382 |
| 192.168.10.102 | 6383、6384 |
| 192.168.10.103 | 6385、6386 |
8. Cluster 节点配置
以 6381 为例:
bind 0.0.0.0
protected-mode no
port 6381
daemonize yes
requirepass 111111
masterauth 111111
logfile "/myredis/cluster/cluster6381.log"
pidfile /myredis/cluster/cluster6381.pid
dir /myredis/cluster
dbfilename dump6381.rdb
appendonly yes
appendfilename "appendonly6381.aof"
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-node-timeout 5000
关键配置:
| 配置 | 说明 |
|---|---|
| cluster-enabled yes | 开启 Cluster 模式 |
| cluster-config-file | 节点自动维护的集群配置文件,不要手工编辑 |
| cluster-node-timeout | 节点超时时间 |
| masterauth | 节点之间复制认证 |
| requirepass | 客户端访问密码 |
注意:每个实例的 port、logfile、pidfile、dbfilename、cluster-config-file 都要区分开。
Cluster 节点之间除了客户端端口,还会使用 cluster bus 端口进行 gossip、故障检测和拓扑传播。默认 bus 端口通常是服务端口加 10000,例如 6381 对应 16381。防火墙、安全组、Docker 端口映射都要同时放行客户端端口和 bus 端口。
多网卡、Docker、NAT 或云服务器环境中,还应显式配置 announce 地址,避免节点向客户端或其他节点发布不可达地址:
cluster-announce-ip 192.168.10.101
cluster-announce-port 6381
cluster-announce-bus-port 16381
启动 6 个实例:
redis-server /myredis/cluster/redis6381.conf
redis-server /myredis/cluster/redis6382.conf
redis-server /myredis/cluster/redis6383.conf
redis-server /myredis/cluster/redis6384.conf
redis-server /myredis/cluster/redis6385.conf
redis-server /myredis/cluster/redis6386.conf
# 启动 cluster
[root@hadoop100 ~]# redis-server /myredis/cluster/redis6383.conf
[root@hadoop100 ~]# redis-server /myredis/cluster/redis6384.conf
[root@hadoop100 ~]# ps -ef|grep redis
root 8473 1 0 15:12 ? 00:00:00 redis-server 0.0.0.0:6383 [cluster]
root 8479 1 0 15:12 ? 00:00:00 redis-server 0.0.0.0:6384 [cluster]
root 8485 7087 0 15:12 pts/0 00:00:00 grep --color=auto redis
[root@hadoop100 ~]#
9. 创建集群
使用 redis-cli –cluster create:
redis-cli -a 111111 --cluster create \
192.168.10.101:6381 \
192.168.10.101:6382 \
192.168.10.102:6383 \
192.168.10.102:6384 \
192.168.10.103:6385 \
192.168.10.103:6386 \
--cluster-replicas 1
–cluster-replicas 1 表示每个 master 配 1 个 replica。
创建集群命令执行示例:
[root@hadoop100 cluster]# redis-cli -a 111111 --cluster create --cluster-replicas 1 \
192.168.10.101:6381 192.168.10.101:6382 \
192.168.10.102:6383 192.168.10.102:6384 \
192.168.10.103:6385 192.168.10.103:6386
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.10.102:6384 to 192.168.10.101:6381
Adding replica 192.168.10.103:6386 to 192.168.10.102:6383
Adding replica 192.168.10.101:6382 to 192.168.10.103:6385
M: f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece 192.168.10.101:6381
slots:[0-5460] (5461 slots) master
S: 60a1a701913fc57b7034dae5e9f65fa4e505f988 192.168.10.101:6382
replicates c9341645f3059c2f95bc4049800020f780ee81a9
M: cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 192.168.10.102:6383
slots:[5461-10922] (5462 slots) master
S: da15381df02c984c7d0016eba332d15d86d29db3 192.168.10.102:6384
replicates f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece
M: c9341645f3059c2f95bc4049800020f780ee81a9 192.168.10.103:6385
slots:[10923-16383] (5461 slots) master
S: 8152314dd86642e657a9e2f8bfecf2a591425dce 192.168.10.103:6386
replicates cd2450b7ae7335b25e9806dd1d7ecfb789f5d282
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
>>> Performing Cluster Check (using node 192.168.10.101:6381)
M: f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece 192.168.10.101:6381
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 8152314dd86642e657a9e2f8bfecf2a591425dce 192.168.10.103:6386
slots: (0 slots) slave
replicates cd2450b7ae7335b25e9806dd1d7ecfb789f5d282
S: da15381df02c984c7d0016eba332d15d86d29db3 192.168.10.102:6384
slots: (0 slots) slave
replicates f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece
S: 60a1a701913fc57b7034dae5e9f65fa4e505f988 192.168.10.101:6382
slots: (0 slots) slave
replicates c9341645f3059c2f95bc4049800020f780ee81a9
M: c9341645f3059c2f95bc4049800020f780ee81a9 192.168.10.103:6385
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 192.168.10.102:6383
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[root@hadoop100 cluster]#
查看集群:
redis-cli -c -p 6381 -a 111111 cluster nodes
redis-cli -c -p 6381 -a 111111 cluster info
节点状态:
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.10.102,port=6384,state=online,offset=434,lag=1
master_failover_state:no-failover
master_replid:a4f0b88ee15ebd9f9081f994abe9adfeb69751b5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:434
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:434
127.0.0.1:6381> cluster nodes
8152314dd86642e657a9e2f8bfecf2a591425dce 192.168.10.103:6386@16386 slave cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 0 <pong-recv> 3 connected
da15381df02c984c7d0016eba332d15d86d29db3 192.168.10.102:6384@16384 slave f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece 0 <pong-recv> 1 connected
60a1a701913fc57b7034dae5e9f65fa4e505f988 192.168.10.101:6382@16382 slave c9341645f3059c2f95bc4049800020f780ee81a9 0 <pong-recv> 5 connected
f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece 192.168.10.101:6381@16381 myself,master - 0 <pong-recv> 1 connected 0-5460
c9341645f3059c2f95bc4049800020f780ee81a9 192.168.10.103:6385@16385 master - 0 <pong-recv> 5 connected 10923-16383
cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 192.168.10.102:6383@16383 master - 0 <pong-recv> 3 connected 5461-10922
127.0.0.1:6381>
#info replication:查看当前 6381 节点的主从复制信息。
#cluster nodes:查看 Redis Cluster 中所有节点的信息。
10. Cluster 读写和 MOVED 重定向
如果不加 -c 直接连某个节点写 key,可能遇到:
MOVED slot ip:port
原因是当前节点不负责这个 key 所在 slot。
如果没有开启 Cluster 模式,可能看到类似报错:
127.0.0.1:6381> set k1 v1
(error) MOVED 12706 192.168.10.103:6385
127.0.0.1:6381>
127.0.0.1:6381> get k1
(error) MOVED 12706 192.168.10.103:6385
127.0.0.1:6381>
127.0.0.1:6381> set k2 v2
OK
127.0.0.1:6381> get k2
"v2"
127.0.0.1:6381>
这段表示:k1 所在的哈希槽是 12706,这个槽不在当前 6381 节点上,而是在 192.168.10.103:6385 节点上,所以返回 MOVED 重定向提示。
而 k2 所在的槽属于当前 6381 节点,所以 set k2 v2 和 get k2 可以正常执行。
MOVED 和 ASK 的含义不同:
| 重定向 | 含义 | 常见场景 |
|---|---|---|
| MOVED | 永久重定向,表示 slot 已经归属另一个节点 | slot 归属已经稳定变更 |
| ASK | 临时重定向,表示 slot 正在迁移过程中 | reshard / migration 期间 |
客户端收到 MOVED 后应更新本地 slot cache;收到 ASK 时通常只对当前命令临时跳转,不应立即把 slot 归属永久改掉。
解决方式是使用 Cluster 模式客户端:
redis-cli -c -p 6381 -a 111111
-c 开启后,redis-cli 会根据重定向自动跳转。
127.0.0.1:6381> quit
[root@hadoop100 cluster]# redis-cli -a 111111 -p 6381 -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6381> set k1 v1
-> Redirected to slot [12706] located at 192.168.10.103:6385
OK
192.168.10.103:6385> keys *
1) "k1"
192.168.10.103:6385>
查看 key 所属 slot:
cluster keyslot k1
11. Cluster 故障转移
Cluster 中每个 master 可以有 replica。如果某个 master 宕机,它的 replica 会在集群投票后晋升为 master。

验证过程:
# 关闭某个 master
redis-cli -p 6381 -a 111111 shutdown
# 从其他节点查看
redis-cli -c -p 6383 -a 111111 cluster nodes
切换后,原 master 的 replica 会变成 master。
Cluster failover 依赖集群内 master 节点的投票和多数派判断,不是单个 replica 自己决定上位。新 master 上位后,会通过 cluster bus 向其他节点广播新的拓扑和 slot 归属。
# Cluster failover 状态 1
127.0.0.1:6382> cluster nodes
8152314dd86642e657a9e2f8bfecf2a591425dce 192.168.10.103:6386@16386 slave cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 0 <pong-recv> 3 connected
f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece 192.168.10.101:6381@16381 master,fail - <ping-sent> <pong-recv> 1 disconnected
da15381df02c984c7d0016eba332d15d86d29db3 192.168.10.102:6384@16384 master - 0 <pong-recv> 7 connected 0-5460
c9341645f3059c2f95bc4049800020f780ee81a9 192.168.10.103:6385@16385 master - 0 <pong-recv> 5 connected 10923-16383
60a1a701913fc57b7034dae5e9f65fa4e505f988 192.168.10.101:6382@16382 myself,slave c9341645f3059c2f95bc4049800020f780ee81a9 0 <pong-recv> 5 connected
cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 192.168.10.102:6383@16383 master - 0 <pong-recv> 3 connected 5461-10922
127.0.0.1:6382>
# 这段表示:原来的主节点 192.168.10.101:6381 已经处于 master,fail 状态,并且连接状态是 disconnected;它的从节点 192.168.10.102:6384 已经上位成为新的 master,接管了原来 0-5460 这段哈希槽。
# Cluster failover 状态 2
[root@hadoop100 cluster]# redis-server /myredis/cluster/redis6381.conf
[root@hadoop100 cluster]# redis-cli -a 111111 -p 6381 -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6381> cluster nodes
cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 192.168.10.102:6383@16383 master - 0 <pong-recv> 3 connected 5461-10922
da15381df02c984c7d0016eba332d15d86d29db3 192.168.10.102:6384@16384 master - 0 <pong-recv> 7 connected 0-5460
c9341645f3059c2f95bc4049800020f780ee81a9 192.168.10.103:6385@16385 master - 0 <pong-recv> 5 connected 10923-16383
60a1a701913fc57b7034dae5e9f65fa4e505f988 192.168.10.101:6382@16382 slave c9341645f3059c2f95bc4049800020f780ee81a9 0 <pong-recv> 5 connected
f4c9b49e94c1c3c9d2fd59e3f7e4650ec1148ece 192.168.10.101:6381@16381 myself,slave da15381df02c984c7d0016eba332d15d86d29db3 0 <pong-recv> 7 connected
8152314dd86642e657a9e2f8bfecf2a591425dce 192.168.10.103:6386@16386 slave cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 0 <pong-recv> 3 connected
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:192.168.10.102
master_port:6384
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:1537
slave_repl_offset:1537
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:a7b351abd8c22099a342dbb9e99f01822d927c7f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1537
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1510
repl_backlog_histlen:28
127.0.0.1:6381>
# 这段表示:6381 节点重新启动后,没有重新成为 master,而是以 slave 的身份重新加入集群。
# 它现在复制的新 master 是:192.168.10.102:6384
# 也就是说,集群故障转移后,原来的 master 6381 恢复上线时,会作为从节点回归,不会自动抢回主节点身份。
注意:Cluster 故障转移也不能保证数据零丢失,因为 master 到 replica 的复制仍然可能有延迟。
12. Cluster 扩容
扩容大致分两步:
- 新节点加入集群。
- 从旧 master 迁移一部分 slot 给新 master。
加入新节点:
redis-cli -a 111111 --cluster add-node \
192.168.10.104:6387 \
192.168.10.101:6381
迁移 slot:
redis-cli -a 111111 --cluster reshard 192.168.10.101:6381
reshard 的最小迁移单位是 slot,但迁移过程中会逐个搬迁该 slot 内的 key。迁移期间客户端可能看到短暂的 ASK 或 MOVED 增多,这是正常现象。生产环境做 slot rebalance 时要避开高峰,并监控延迟、重定向次数和慢查询。
迁移完成后检查:
redis-cli -a 111111 --cluster check 192.168.10.101:6381
13. Cluster 缩容
缩容时不能直接关闭节点。正确步骤是:
- 先把要下线 master 上的 slot 迁移到其他 master。
- 确认该节点不再负责 slot。
- 执行 del-node 移除节点。
- 再停止 Redis 实例。
如果节点仍然持有 slot,不能直接 del-node。否则会破坏 slot 覆盖,导致 cluster_state 进入异常状态。
命令示例:
redis-cli -a 111111 --cluster reshard 192.168.10.101:6381
redis-cli -a 111111 --cluster del-node 192.168.10.101:6381 <node-id>
14. Cluster 常用命令
| 命令 | 说明 |
|---|---|
| cluster nodes | 查看节点 |
| cluster info | 查看集群状态 |
| cluster slots | 查看 slot 分布 |
| cluster keyslot key | 查看 key 属于哪个 slot |
| cluster myid | 查看当前节点 ID |
| cluster failover | 手动故障转移 |
| redis-cli –cluster check host:port | 检查集群 |
| redis-cli –cluster reshard host:port | 迁移 slot |
| redis-cli –cluster add-node | 添加节点 |
| redis-cli –cluster del-node | 删除节点 |
下面是 Redis Cluster 源码中 hash tag 处理逻辑的简化阅读示例:
unsigned int keyHashSlot(char *key, int keylen) {
int s, e; /* start-end indexes of { and } */
for (s = 0; s < keylen; s++)
if (key[s] == '{') break;
/* No '{' ? Hash the whole key. This is the base case. */
if (s == keylen) return crc16(key, keylen) & 0x3FFF;
/* '{' found? Check if we have the corresponding '}'. */
for (e = s + 1; e < keylen; e++)
if (key[e] == '}') break;
/* No '}' or nothing between {} ? Hash the whole key. */
if (e == keylen || e == s + 1) return crc16(key, keylen) & 0x3FFF;
/* If we are here there is both a { and a } on its right. Hash
* what is in the middle between { and }. */
return crc16(key + s + 1, e - s - 1) & 0x3FFF;
}
15. Java 客户端选择:Jedis、Lettuce、RedisTemplate
服务端 Cluster 搭好后,应用侧还要正确接入。
常见 Java 客户端:
| 客户端 | 说明 |
|---|---|
| Jedis | 传统 Java 客户端,API 直观。单个 Jedis 连接不是线程安全的,生产上通常配合连接池 |
| Lettuce | 基于 Netty,支持同步、异步、响应式,Spring Boot 2.x 默认使用 |
| RedisTemplate | Spring Data Redis 提供的高级模板,底层可以使用 Lettuce 或 Jedis |
| StringRedisTemplate | RedisTemplate 的字符串版本,适合 key/value 都是字符串的常见场景 |
Jedis 和 Lettuce 都是 Redis 的客户端,它们都可以连接 Redis 服务器。
但是在 Spring Boot 2.0 之后,默认使用的是 Lettuce 这个客户端连接 Redis 服务器。
当使用 Jedis 客户端连接 Redis 服务器时,每个线程都要拿自己创建的 Jedis 实例去连接 Redis 客户端。
当有很多线程时,不仅开销大,需要反复创建和关闭 Jedis 连接,而且 Jedis 也是线程不安全的。一个线程通过 Jedis 实例更改 Redis 服务器中的数据之后,可能会影响另一个线程。
但是如果使用 Lettuce 客户端连接 Redis 服务器,就不会出现上面的问题。
Lettuce 底层使用的是 Netty。当多个线程都需要连接 Redis 服务器时,可以保证只创建一个 Lettuce 连接,让所有线程共享这一个 Lettuce 连接。
这样可以减少创建和关闭连接的开销,而且这种方式也是线程安全的,不会出现一个线程通过 Lettuce 修改 Redis 服务器中的数据之后影响另一个线程的情况。
如果是 Spring Boot 项目,通常优先使用 spring-boot-starter-data-redis,底层默认走 Lettuce。Jedis 也支持 Cluster,但连接模型和线程使用方式不同;Lettuce 更适合 Spring Boot 默认的共享连接与异步能力。
16. Spring Boot 2.7 单机 Redis 配置
Maven 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
application.yml:
spring:
redis:
database: 0
host: 192.168.10.101
port: 6379
password: 111111
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
如果是 Spring Boot 3.x,配置前缀改为:
spring:
data:
redis:
host: 192.168.10.101
port: 6379
也就是说,Boot 2.x 常见是 spring.redis,Boot 3.x 常见是 spring.data.redis。
17. RedisTemplate 序列化配置
很多旧示例会用 Jackson2JsonRedisSerializer 加 activateDefaultTyping。这类配置容易引入反序列化安全风险,也会让 value 中出现 Java 类型信息。
如果没有特殊需求,可以优先使用 StringRedisTemplate。如果要存 JSON,可以由业务明确序列化成字符串。
示例:
@Service
public class OrderCacheService {
private static final String ORDER_KEY_PREFIX = "ord:";
private final StringRedisTemplate stringRedisTemplate;
public OrderCacheService(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public void addOrder(String orderId, String json) {
stringRedisTemplate.opsForValue().set(ORDER_KEY_PREFIX + orderId, json);
}
public String getOrder(String orderId) {
return stringRedisTemplate.opsForValue().get(ORDER_KEY_PREFIX + orderId);
}
}
如果确实需要自定义 RedisTemplate<String, Object>,建议显式控制序列化策略,并避免反序列化不可信数据。
18. Spring Boot 2.7 连接 Redis Cluster
Cluster 配置:
spring:
redis:
password: 111111
cluster:
max-redirects: 3
nodes:
- 192.168.10.101:6381
- 192.168.10.101:6382
- 192.168.10.102:6383
- 192.168.10.102:6384
- 192.168.10.103:6385
- 192.168.10.103:6386
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
Boot 3.x 使用 spring.data.redis 前缀,其他层级基本类似。
应用访问集群时,底层客户端会根据 slot 和重定向访问正确节点。
RedisTemplate 本身不负责 Cluster 路由,真正处理 slot cache、MOVED / ASK 和拓扑刷新的是底层连接工厂与客户端实现。只要 Spring Data Redis 正确配置为 Cluster 模式,RedisTemplate 和 StringRedisTemplate 会通过底层客户端适配 Cluster 拓扑。
19. Lettuce 拓扑刷新问题
这里最需要关注的问题是:Redis Cluster 服务端已经完成故障转移,但 Spring Boot 客户端没有及时感知拓扑变化。
现象是:
- 手动关闭某个 master。
- Cluster 中对应 replica 自动晋升为新 master。
- redis-cli 使用 Cluster 模式验证读写正常。
- Spring Boot 客户端继续访问时,仍然可能访问旧拓扑,出现连接或重定向异常。
服务端验证示例:
127.0.0.1:6384> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:4d42b55e7c355e4cea426446fe1a8b70581d1ea1
master_replid2:87e127681abb244616ecc1c26820fcd9a160a667
master_repl_offset:16022
second_repl_offset:16023
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:16022
原来的 192.168.10.101:6381 节点已经无法连接,提示 Connection refused。
集群检查后发现:
192.168.10.102:6384 已经顺利上位成为 master,
并接管了原来 6381 负责的部分哈希槽。
最终结果:
[OK] All 16384 slots covered.
表示集群所有哈希槽仍然完整覆盖,集群状态正常。
[root@hadoop100 ~]# redis-cli -a 111111 --cluster check 192.168.10.101:6382
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Could not connect to Redis at 192.168.10.101:6381: Connection refused
192.168.10.102:6384 (da15381d...) -> 1 keys | 8188 slots | 0 slaves.
192.168.10.103:6385 (c9341645...) -> 1 keys | 4098 slots | 1 slaves.
192.168.10.102:6383 (cd2450b7...) -> 5 keys | 4098 slots | 1 slaves.
[OK] 7 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.10.101:6382)
S: 60a1a701913fc57b7034dae5e9f65fa4e505f988 192.168.10.101:6382
slots: (0 slots) slave
replicates c9341645f3059c2f95bc4049800020f780ee81a9
S: 8152314dd86642e657a9e2f8bfecf2a591425dce 192.168.10.103:6386
slots: (0 slots) slave
replicates cd2450b7ae7335b25e9806dd1d7ecfb789f5d282
M: da15381df02c984c7d0016eba332d15d86d29db3 192.168.10.102:6384
slots:[0-6824],[10923-12285] (8188 slots) master
M: c9341645f3059c2f95bc4049800020f780ee81a9 192.168.10.103:6385
slots:[12286-16383] (4098 slots) master
1 additional replica(s)
M: cd2450b7ae7335b25e9806dd1d7ecfb789f5d282 192.168.10.102:6383
slots:[6825-10922] (4098 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[root@hadoop100 ~]#
故障演练中,Redis Cluster 集群部署采用 3 主 3 从拓扑结构,数据读写访问 master 节点,replica 节点负责备份。
当 master 宕机并完成主从切换后,Redis 手动操作是正常的,但是在 Java / Spring Boot 项目中,可能会出现两个经典故障。
19.1 Redis 命令执行超时
[http-nio-7777-exec-7] ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]
- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;
nested exception is org.springframework.dao.QueryTimeoutException: Redis command timed out;
nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 1 minute(s)] with root cause
io.lettuce.core.RedisCommandTimeoutException: Command timed out after 1 minute(s)
at io.lettuce.core.internal.ExceptionFactory.createTimeoutException(ExceptionFactory.java:59)
at io.lettuce.core.internal.Futures.awaitOrCancel(Futures.java:246)
at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:74)
at org.springframework.data.redis.connection.lettuce.LettuceConnection.await(LettuceConnection.java:1061)
at org.springframework.data.redis.connection.lettuce.LettuceConnection.lambda$doInvoke$4(LettuceConnection.java:92)
at org.springframework.data.redis.connection.lettuce.LettuceInvoker$Synchronizer.invoke(LettuceInvoker.java:673)
这个报错的核心是:
RedisCommandTimeoutException: Command timed out after 1 minute(s)
表示客户端向 Redis 发送命令后,在指定时间内没有收到正常响应,所以出现命令超时。
19.2 Redis 连接失败
[http-nio-7777-exec-4] ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]
- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;
nested exception is org.springframework.data.redis.RedisConnectionFailureException: Redis connection failed;
nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 192.168.10.101:6381] with root cause
这个报错的核心是:
RedisConnectionException: Unable to connect to 192.168.10.101:6381
表示客户端仍然尝试连接旧的 Redis 节点,但是该节点已经不可用,所以连接失败。
19.3 问题原因
Redis Cluster 主从切换后,服务端的集群状态已经恢复正常。
例如手动使用 redis-cli -c 访问 Redis 时,可以自动进行 MOVED 重定向,找到新的 master 节点。
但是 Java 客户端可能仍然缓存着旧的集群拓扑信息。
当旧 master 宕机后,如果客户端没有及时刷新集群拓扑,就可能继续访问旧节点,从而出现:
Command timed out after 1 minute(s)
或者:
Unable to connect to 旧 master IP:端口
这类问题通常和 Redis Cluster 主从切换后,客户端没有及时刷新集群拓扑信息有关。拓扑刷新失败还可能形成 MOVED 风暴:客户端持续访问旧 slot 映射,不断收到重定向,却没有及时更新本地 slot cache。
19.4 故障归因
Redis Cluster 的故障转移可以让服务端自动完成主从切换。
但是客户端也需要及时感知新的集群拓扑。
如果客户端没有及时刷新节点信息,就可能出现:
- 请求还发往旧 master;
- 连接已经下线的节点;
- 命令执行超时;
- Redis 连接失败。
所以在 Spring Boot 使用 Lettuce 连接 Redis Cluster 时,需要特别注意集群拓扑刷新配置。
20. 开启 Lettuce Cluster 拓扑刷新
Spring Boot 2.7 配置:
spring:
redis:
password: 111111
cluster:
max-redirects: 3
nodes:
- 192.168.10.101:6381
- 192.168.10.101:6382
- 192.168.10.102:6383
- 192.168.10.102:6384
- 192.168.10.103:6385
- 192.168.10.103:6386
lettuce:
cluster:
refresh:
adaptive: true
period: 2s
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
两个关键配置:
| 配置 | 说明 |
|---|---|
| spring.redis.lettuce.cluster.refresh.adaptive=true | 开启自适应刷新,遇到重定向、连接异常等事件时刷新拓扑 |
| spring.redis.lettuce.cluster.refresh.period=2s | 开启周期性刷新 |
自适应刷新通常由连接事件、重定向、命令失败等触发;周期性刷新则负责兜底校准拓扑。生产环境建议两者一起使用:adaptive 负责快速响应故障,periodic 负责修复遗漏或长连接缓存造成的拓扑漂移。
max-redirects 也不能设置得过小。它限制单次命令最多跟随多少次重定向,过小可能导致 slot 迁移或 failover 期间请求过早失败。
Spring Boot 3.x 对应前缀:
spring:
data:
redis:
lettuce:
cluster:
refresh:
adaptive: true
period: 2s
生产上不一定要设置成 2 秒,周期太短会增加额外开销。可以按故障切换敏感度设置,比如 5s、10s、30s,并配合自适应刷新。
21. Cluster 接入排查清单
如果应用访问 Redis Cluster 异常,按下面顺序查。
21.1 服务端集群是否健康
redis-cli -a 111111 --cluster check 192.168.10.101:6381
redis-cli -c -p 6381 -a 111111 cluster info
redis-cli -c -p 6381 -a 111111 cluster nodes
重点看:
cluster_state:ok
以及所有 slot 是否被覆盖。
21.2 客户端是否使用 Cluster 模式
命令行必须带 -c:
redis-cli -c -p 6381 -a 111111
应用客户端必须配置 cluster nodes,而不是只配置单机 host/port。
21.3 多 key 命令是否跨 slot
检查:
cluster keyslot key1
cluster keyslot key2
如果业务必须多 key 操作,使用 hash tag:
user:{1001}:name
user:{1001}:age
如果已经出现 CROSSSLOT,说明至少有一个多 key 命令跨了 slot。需要检查 key 设计,而不是只调整客户端配置。
21.4 Lettuce 是否开启拓扑刷新
确认是否配置:
spring:
redis:
lettuce:
cluster:
refresh:
adaptive: true
period: 10s
21.5 NAT、Docker、云服务器地址是否正确
Cluster 节点会互相发布自己的地址。如果部署在 Docker、NAT、云服务器多网卡环境中,要特别检查节点 announce 地址,否则客户端可能拿到不可访问的内网或容器地址。
常见配置:
cluster-announce-ip 192.168.10.101
cluster-announce-port 6381
cluster-announce-bus-port 16381
21.6 区分超时和连接失败
RedisCommandTimeoutException 更偏向命令在指定时间内没有返回,常见原因包括网络抖动、Redis 阻塞、slot 迁移期间重定向过多或客户端拓扑未刷新。
RedisConnectionException 更偏向节点不可达,常见原因包括节点宕机、端口未开放、announce 地址错误、Docker/NAT 映射错误。
22. 本文小结
Redis Cluster 是 Redis 横向扩展的核心方案。
重点结论如下:
- Cluster 使用 16384 个 hash slot 做分片。
- key 的 slot 计算公式是 CRC16(key) mod 16384。
- redis-cli 访问 Cluster 时要加 -c,否则会遇到 MOVED 重定向;生产客户端需要维护 slot cache 并处理 MOVED / ASK。
- 多 key 命令要求 key 在同一个 slot,可以用 hash tag 控制。
- Cluster 自带故障转移,但不保证数据零丢失。
- 扩容缩容的本质是迁移 slot。
- Spring Boot 连接 Cluster 时要配置 cluster nodes。
- Lettuce 默认拓扑刷新不一定满足故障切换需求,生产上建议同时开启 adaptive refresh 和合理的 periodic refresh。
- Docker、NAT、多网卡环境要正确配置 cluster-announce-* 和 cluster bus 端口。
到这里,Redis 基础篇的主线就完整了:安装、数据类型、持久化、事务和 Pipeline、主从复制、Sentinel、Cluster,以及 Java 应用接入。