当前位置: 首页 > news >正文

深入剖析Redis高性能的原因,IO多路复用模型,Redis数据迁移,分布式锁实现

一、深入剖析Redis单线程处理命令仍具备高性能的原因

Redis 虽然是单线程处理命令的(主线程负责网络 I/O 和命令处理),但它依然具备 百万级 QPS 的吞吐能力。这个看似矛盾的现象,其实是 Redis 高性能架构设计底层实现精妙配合的结果。

下面我们从架构、内核原理、操作系统机制、与其他系统对比等多维度深入剖析,为何 Redis 单线程却读写性能极高


1. Redis 是“单线程处理命令”,但不是完全单线程

模块是否多线程说明
主线程✅ 单线程网络请求 + 命令处理
AOF 写盘✅ 单独线程异步写磁盘
RDB 子进程✅ 多进程fork 子进程进行快照
集群复制✅ 多线程主从同步、传输增量数据
I/O 解压压缩(6.0+)✅ 多线程io-threads 支持并行读写处理

👉 结论:“命令执行是单线程”,但 Redis 本质是一个多组件协同的高性能系统。


2. 单线程为何反而高性能?原因如下:

✅ 1. 纯内存操作,跳过磁盘瓶颈

  • Redis 所有数据都存在内存中,命令执行直接操作数据结构,无需 I/O。

  • 内存随机访问速度是磁盘的百万倍(ns vs ms):

    内存:100 ns
    SSD:100 µs
    HDD:10 ms

✅ 2. 事件驱动 + epoll 高效 I/O 多路复用

Redis 使用 Reactor 模型 + epoll 实现网络事件处理:

单线程事件循环:
while (true) {epoll_wait(...) → 返回就绪事件集合遍历处理每个客户端连接的请求
}
  • 非阻塞 I/O,连接不会阻塞线程

  • 没有线程切换上下文开销(节省 CPU)


✅ 3. 高效的数据结构 + 指令执行逻辑极短

Redis 用的是高度优化的数据结构(C 语言实现):

类型底层结构性能特性
StringSDS动态数组,避免频繁 realloc
Hashziplist / dict紧凑结构 + 哈希冲突最小化
ZSet跳表 + dictlog(N) 级别插入与范围查询
Listquicklistziplist + 双向链表
Setintset / dict整数集合内存节省,多数 O(1)

👉 每条命令执行路径都在 100 行以内,执行耗时极短,CPU 缓存命中率高


✅ 4. 避免并发控制成本(无锁优势)

相比多线程系统,Redis 单线程:

  • 无需加锁(没有竞争) → 没有锁等待、死锁、上下文切换

  • 保证串行语义一致性 → 实现原子性和事务机制简单(MULTI)

在高并发场景下,锁开销和线程切换代价比 Redis 单线程要大得多


✅ 5. 高并发下也能支撑百万级 QPS

Redis 单线程可以实现:

  • 10 万级 QPS:普通业务场景

  • 100 万级 QPS:使用流水线批处理 + 简单命令(如 INCR)

  • 实测 Redis 单实例可处理 100k~150k ops/s,在 1ms 内响应


3. 测试实证:性能对比

redis-benchmark -t set,get -n 1000000 -c 100 -P 10

输出结果(示例):

SET: 120000 requests/sec
GET: 130000 requests/sec

说明 Redis 能在单线程下稳定支撑高并发


4. Redis 为何不多线程处理命令?

    命令多线程带来的问题:

  1. 多线程引入锁 → 数据结构加锁 → 性能下降

  2. 多线程之间竞争资源 → 需要线程协调机制(复杂)

  3. 多线程命令顺序不可控 → 难以实现事务和原子操作(MULTI、Lua)


5. Redis 6.0+ 的“多线程 I/O”支持(io-threads)

从 Redis 6.0 开始,加入 io-threads 支持:

  • 网络读写拆分到多线程中(解包 + 编码阶段)

  • 命令执行依然是主线程串行处理

配置方式:

io-threads-do-reads yes
io-threads 4

效果:

  • 降低主线程 CPU 压力

  • 提高网络密集场景性能(比如 pipeline 请求、TLS)


7. 总结:Redis 单线程依然高性能的 5 大核心原因

原因描述
① 内存操作极快全在内存,跳过磁盘 I/O
② 无锁单线程处理避免线程切换与锁开销
③ 高效 I/O 机制epoll + Reactor,异步处理连接
④ 数据结构精简C 实现的结构,执行逻辑极短
⑤ I/O 多线程辅助Redis6+ 解放部分网络线程

二、深入剖析 Redis 中的 IO 多路复用

1. 什么是 IO 多路复用?

IO 多路复用是一种操作系统提供的机制,允许单个线程同时监听多个文件描述符(socket fd),并在任一 fd 准备好时通知应用程序进行读写操作。

这解决了传统阻塞 IO 每个连接都需要一个线程的问题,大幅提升了并发连接处理能力。

举个经典例子:

假设你要监听 100 个客户端 socket,如果用传统模型:

  • 每个 socket 一个线程,开销大、切换频繁。

  • 而 IO 多路复用只需一个线程就能“监听所有连接”!


2. IO 多路复用的系统调用类型

模型系统调用是否跨平台特点
selectselect()最老旧,有 FD 数量限制(1024)
pollpoll()无数量限制,但效率仍低
epollepoll_*()❌(Linux 专有)性能最佳,Redis 默认使用
kqueuekqueue()❌(BSD/OSX)类似 epoll
IOCPWindows 系统Windows 专属 IO 模型

Redis 默认使用的是 Linux 下的 epoll 模型。


3. epoll 模型详细剖析(Redis 用的就是它)

epoll 是 Linux 2.6 之后提供的高性能 IO 事件通知机制,具备如下优势:

✅ 1. 事件驱动

  • 不再轮询每个连接,而是让内核“通知”应用层哪些 socket 有事件。

✅ 2. O(1) 复杂度

  • 与监听的 fd 数量无关,事件到来才触发回调处理。

✅ 3. 边缘触发/水平触发支持

  • Redis 使用 水平触发(Level Triggered),更稳妥。


epoll 使用三步流程(Redis 源码体现):

int epfd = epoll_create();
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);   // 注册事件
epoll_wait(epfd, events, MAX_EVENTS, timeout); // 阻塞等待事件发生

4. Redis 是如何利用 IO 多路复用的?

Redis 中的核心事件循环基于一个通用的 IO 多路复用抽象层 ae,底层实现根据平台选择:

  • Linux:ae_epoll.c

  • macOS:ae_kqueue.c

  • BSD:ae_select.c

核心事件循环(简化流程):

while (1) {// 1. 等待 socket 就绪(读/写)fired = aeApiPoll(...);// 2. 处理可读事件(客户端命令请求)processInputBuffer();// 3. 执行命令逻辑(SET/GET 等)// 4. 可写事件响应结果(发送给客户端)sendReplyToClient();
}

对应 Redis 的 ae.c 框架核心模块:

模块功能
aeCreateEventLoop创建事件循环
aeCreateFileEvent注册文件事件(读写)
aeProcessEvents主循环处理事件
aeMainRedis 主线程主循环所在

5. 为何 IO 多路复用能显著提升 Redis 性能?

传统多线程IO 多路复用
每连接一个线程,线程切换频繁单线程异步监听所有连接
上下文切换消耗大没有线程切换
需加锁,存在竞争无锁逻辑,效率高
并发连接量受限支持百万连接并发

6. Redis 中 IO 多路复用与命令执行是如何分工的?

操作阶段是否多线程IO 多路复用角色
网络读取请求✅ Redis6 可多线程epoll 通知可读事件
命令解析与执行❌ 主线程串行处理由主线程处理 buffer
网络返回响应✅ Redis6 可多线程epoll 通知可写事件

7. epoll + IO 多路复用真实效果

  • 单线程监听 + 处理十万并发连接是常态。

  • 每个 socket 都是非阻塞处理,避免任何阻塞操作。

  • Redis 对外响应延迟常常在 亚毫秒级别


8. 实战场景优化建议

场景建议
高并发短连接使用 pipeline 减少 RTT
高连接数优化 ulimit -n,避免 fd 被耗尽
网络负载高开启 io-threads 多线程读写
超大 key 导致事件阻塞拆分数据结构,限制 key 大小

9. 总结

特性描述
模型IO 多路复用(epoll)
优点高并发、低延迟、无锁、无阻塞
结合点单线程模型完美结合 epoll
Redis 效果百万级连接吞吐,高速低延迟响应

三、分析在 Redis Cluster 集群中,当某个节点挂掉时,如何保证实现高可用

1. Redis Cluster 的基本架构回顾

Redis Cluster 由多个节点组成,每个节点负责一部分 16384 个槽(hash slot)。集群中每个主节点(master)可以有 1 个或多个从节点(slave/replica)组成复制关系。

例如:

M1 负责 slot 0~5460     ←—— R1 (M1 的 replica)
M2 负责 slot 5461~10922 ←—— R2 (M2 的 replica)
M3 负责 slot 10923~16383←—— R3 (M3 的 replica)

2. 节点挂掉后的高可用流程概述

场景:假设 M1 节点突然宕机

目标:自动将 M1 的副本节点 R1 提升为新的主节点

高可用触发条件与流程:

阶段细节
1️⃣ 故障发现节点之间通过 gossip 协议定期发送 PING/PONG
2️⃣ 主观下线(PFAIL)某个节点收到 M1 的超时响应后,标记其为“主观下线”
3️⃣ 客观下线(FAIL)如果半数以上主节点也检测到 M1 超时 → 宣布 M1 为“客观下线”
4️⃣ 选主R1、R1' 等副本竞争成为新的 master
5️⃣ 故障转移由胜出的 R1 发起 failover,接管 M1 的 slot 并广播更新
6️⃣ 更新拓扑所有节点更新 cluster 配置,slot → R1,新 master 上线继续服务

3. 深入每个阶段细节

✅ 1. Gossip 探测机制(节点故障检测基础)

  • 每个 Redis 节点定时随机探测其他节点(PING/PONG)

  • 如果 N 秒未响应,就将其标记为 PFAIL(主观下线)

  • 默认超时时间 cluster-node-timeout,如 15 秒

✅ 2. FAIL 机制(客观下线)

  • 若大多数主节点也认为某节点为 PFAIL → 客观下线(FAIL)

  • 节点在 cluster bus 中广播 FAIL 消息,告诉其他节点该节点已宕机

  • 不依赖中心节点,全是分布式一致性投票

✅ 3. 自动 Failover(故障转移)

当主节点 FAIL,副本会发起竞选,选出一个副本进行主从切换:

步骤描述
Step1副本等待随机时间,发送 FAILOVER AUTH REQUEST 给其他主节点
Step2多数主节点响应同意(给票)
Step3获得多数票后,副本执行 slaveof no one 成为新的 master
Step4通过 cluster bus 广播新的 slot 分配关系(slot → 新主节点)

选主规则:

Redis Cluster 中,优先选:

  • 副本延迟最小(replica-priority 高)

  • 数据最全(复制偏移量最大)


4. 客户端连接高可用机制

Redis Cluster 使用 MOVED 重定向 + 客户端缓存节点槽信息 实现透明重连:

  • 如果客户端访问了已经挂掉的主节点的 slot

  • 集群中其他节点响应 MOVED slot ip:port

  • 客户端更新槽位映射并重发请求 → 自动路由到新主节点(R1)

高级客户端(如 Jedis、Lettuce)默认支持这一机制。


5. 如何确保切换过程数据不丢?

关键机制:

  1. 主从复制机制

    • 从节点异步复制主节点数据

    • 一般复制延迟在毫秒级,丢失极少

  2. 复制偏移量对比选主

    • 偏移量大的副本优先(说明数据更全)

  3. AOF + RDB 持久化(如果开启)

    • 提高宕机节点恢复可能性


6. 注意事项与潜在问题

问题说明
数据延迟异步复制有可能导致极端情况丢失最后几条数据
所有副本不可用如果所有副本都宕机,slot 将不可用,必须人工修复
分区脑裂网络分区时,主从同时可写可能导致数据不一致(Redis 禁止这种场景自动切主)
配置不当replica-priority=0 的副本不会参与主选举;务必正确设置

7. 最佳实践(高可用保障)

  1. 每个主节点至少配置 1 个副本

  2. 副本部署在不同机器/机房,避免单点故障

  3. 开启 AOF(append-only)提高数据恢复能力

  4. 客户端使用支持 MOVED 重定向的 SDK

  5. 合理配置 cluster-node-timeout(推荐 15~30s)


8. 总结图示(主节点挂掉后的流程):

        +----------+             +----------+|  M1:主   | <—— Replication ——> |  R1:从   |+----------+             +----------+↓宕机其他主节点 PING 超时↓多数节点确认 FAIL↓R1 请求投票↓获得多数选票↓R1 → master,广播新的 slot 映射↓客户端收到 MOVED,重试请求到 R1

四、剖析Redis Cluster 模式中,节点变化时数据迁移

1. Redis Cluster 的数据分布核心机制:哈希槽 slot

  • Redis Cluster 将键的空间划分为 0 ~ 1638316384 个槽(slot)

  • 每个节点持有部分槽,比如:

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

当你新增一个节点时,需要将部分槽(slot)从已有节点迁移到新节点,同时将这些 slot 对应的数据也迁移过去。


2. 新增节点是否自动迁移数据?

不会自动迁移,Redis Cluster 不具备自动重分布功能。

你需要手动进行以下步骤:

  1. 向集群中添加新节点

  2. 指定要迁移的槽位范围

  3. 将这些槽从旧节点迁移到新节点(槽迁移 + 数据迁移

Redis 官方推荐使用 redis-cli 提供的以下命令:

# 添加节点
redis-cli --cluster add-node NEW_HOST:PORT EXISTING_HOST:PORT# 重分片槽并迁移数据
redis-cli --cluster reshard EXISTING_HOST:PORT

3. 数据迁移(槽迁移)过程详解

Redis Cluster 使用 槽状态MIGRATE 命令 实现数据从旧节点到新节点的迁移。

✅ 槽位迁移的3个状态

每个迁移中的槽位,涉及两种节点:

节点状态
源节点MIGRATING
目标节点IMPORTING

✅ 数据迁移原理

  • Redis 使用 MIGRATE 命令从源节点复制 key 到目标节点

  • 源节点逐个扫描属于该 slot 的 key,并将其迁移

  • 每迁移一个 key,源节点删除该 key

✅ 示例命令(迁移槽 5460 从 node1 到 node4)

# 设置源节点为 MIGRATING 状态
CLUSTER SETSLOT 5460 MIGRATING node4_id# 设置目标节点为 IMPORTING 状态
CLUSTER SETSLOT 5460 IMPORTING node1_id# 使用 MIGRATE 命令迁移 key
MIGRATE node4_ip port key 0 timeout

通常这些由 redis-cli --cluster reshard 自动处理。


4. 迁移过程中客户端如何感知和处理?

迁移过程 不会阻塞客户端读写,Redis Cluster 采用了:

MOVED 和 ASK 重定向机制:

状况响应类型客户端行为
槽迁移已完成MOVED slot new_ip:port客户端更新槽映射,重发请求
正在迁移过程中ASK slot new_ip:port客户端先向新节点发送 ASKING 再发命令

这保证了 数据一致性读写不中断

客户端 SDK 处理:

  • 高级客户端(如 Jedis、Lettuce)自动识别 ASK/MOVED 并自动重试

  • 客户端会缓存 slot → 节点的映射表,定期或出错时刷新


5. 节点减少时(下线节点)怎么迁移数据?

  • 使用 redis-cli --cluster reshard 将要下线节点的 slot 迁移到其他节点

  • 确保该节点不再持有 slot 后,执行:

redis-cli --cluster del-node EXISTING_HOST:PORT NODE_ID

强制下线未迁移完 slot 的节点,会导致数据丢失!


6. 迁移期间的注意事项

问题说明
并发迁移压力数据量大时建议分批迁移 slot,避免带宽/内存压力过大
读写冲突Redis 的 slot 状态机制 + ASK 保证请求正确重定向
高可用保障避免同时迁移多个 master 的 slot,容易产生重负载节点
客户端异常使用支持自动重定向的客户端,非标准客户端会出错

7. 实战流程总览:新增节点后重分片

# 1. 启动新节点
redis-server --port 7004 --cluster-enabled yes --cluster-config-file nodes.conf --appendonly yes# 2. 加入集群
redis-cli --cluster add-node 127.0.0.1:7004 127.0.0.1:7000# 3. 重分片 slot 到新节点(如分 4000 个 slot)
redis-cli --cluster reshard 127.0.0.1:7000
# 输入 4000,选择源节点,目标节点,确认执行# 4. 完成后集群结构更新
redis-cli --cluster info 127.0.0.1:7000

8. 总结

问题答案
新增/删除节点是否自动迁移数据?❌ 不自动,需手动 reshard
数据迁移期间客户端是否可用?✅ 可用,依赖 ASK/MOVED 重定向
客户端如何正确处理?使用支持 Cluster 的 SDK(Jedis、Lettuce 等)
Redis 如何实现无缝迁移?使用 MIGRATING/IMPORTING + MIGRATE 命令逐 key 搬迁

五、数据迁移期间,如何保证客户端正常读写请求

在 Redis Cluster 中进行 数据迁移(如槽位 reshard 或节点 扩容/缩容)时,涉及多个关键流程,包括:

  1. 槽位(slot)的迁移

  2. 键值数据(key-value)的迁移

  3. 客户端请求处理(ASK/MOVED 重定向)


1. 数据迁移流程总览(slot & key)

假设:我们要把 slot 1000 从节点 A 迁移到节点 B

Redis 的数据迁移包含两个阶段:

阶段操作描述
1️⃣ 槽位迁移准备设置槽状态节点 A 标记为 MIGRATING, B 标记为 IMPORTING
2️⃣ 数据迁移执行迁移 keyA 使用 MIGRATE 命令将 slot=1000 的 key 搬到 B

2. 操作细节详解

✅ 第一步:标记槽的迁移状态

# 在源节点 A 上执行:
CLUSTER SETSLOT 1000 MIGRATING <B的node_id># 在目标节点 B 上执行:
CLUSTER SETSLOT 1000 IMPORTING <A的node_id>

作用:

  • 告诉集群:此 slot 正在被从 A 迁移到 B

  • 迁移状态使得集群中的其他节点也能感知槽状态变化


✅ 第二步:数据迁移(使用 MIGRATE)

源节点(A)逐个将属于 slot 1000 的 key 搬到目标节点(B):

# 对每个 key 执行
MIGRATE B_HOST B_PORT key 0 timeout [COPY] [REPLACE] KEYS key

内部流程:

  1. 源节点 A 打开与目标节点 B 的连接

  2. 源节点将 key 的数据序列化(RDB 编码)

  3. 通过 socket 传输给目标节点

  4. 目标节点将 key 写入本地内存

  5. 源节点删除本地 key(除非加 COPY

🔁 这个过程是逐个 key 迁移的,所以数据量大时需要分批迁移避免阻塞。


✅ 第三步:迁移完成,设置槽正式归属

# 所有 key 搬迁完后,在集群内广播 slot 所属更新
CLUSTER SETSLOT 1000 NODE <B的node_id>

这样所有节点都会知道:slot 1000 现在属于节点 B。


3. 迁移期间客户端请求处理流程(核心!)

正常情况下:

客户端缓存有:slot → 节点 映射,比如:

slot 1000 → A

迁移期间,如果客户端访问了迁移中的 slot:

1. 读/写命令发给原节点 A
  • 1000 被标记为 MIGRATING

  • Redis 返回错误:

-ASK 1000 <B的ip:port>
2. 客户端处理 ASK 响应

客户端执行以下流程:

# Step 1: 发送 ASKING 命令(告知 B 临时允许访问此 slot)
ASKING# Step 2: 重新发送原始命令(GET、SET 等)给 B
GET user:1234
  • ASKING 是 Redis 的临时许可机制,让目标节点 B 接收未完成迁移 slot 的请求

  • 一旦迁移完成,客户端将收到 MOVED 指令并更新槽映射

3. 客户端更新 slot 缓存

如果迁移已经结束,Redis 返回:

-MOVED 1000 <B的ip:port>

客户端收到 MOVED 后,刷新本地槽位映射表。


4. 流程图:客户端如何处理迁移过程中的请求?

客户端 --- GET user:1234 ---> 源节点 A (slot 1000 MIGRATING)|<---- -ASK 1000 B_ip:port|
客户端 ---> ASKING + GET user:1234 ---> 目标节点 B (IMPORTING)|<---- key result

5. 完整实战迁移流程(命令级演示)

# 1. 查询 key 的槽位(确认是 slot 1000)
redis-cli -c cluster keyslot user:1234# 2. 在 A 上标记为 MIGRATING
redis-cli -c -h A_IP -p A_PORT cluster setslot 1000 migrating B_NODE_ID# 3. 在 B 上标记为 IMPORTING
redis-cli -c -h B_IP -p B_PORT cluster setslot 1000 importing A_NODE_ID# 4. 找出 A 中 slot=1000 的所有 key(可用 scan + keyslot)
redis-cli -c -h A_IP -p A_PORT --scan | while read key; doSLOT=$(redis-cli -c cluster keyslot "$key")if [[ "$SLOT" -eq 1000 ]]; thenredis-cli -c -h A_IP -p A_PORT migrate B_IP B_PORT "$key" 0 5000fi
done# 5. 设置 slot 归属权
redis-cli -c -h A_IP -p A_PORT cluster setslot 1000 node B_NODE_ID

redis-cli --cluster reshard 工具其实是自动化做了上面所有事情。


6. 总结:Redis Cluster 数据迁移核心点

维度描述
是否自动迁移❌ 不自动,需要手动或工具迁移
迁移粒度按 slot,slot 包含多个 key
客户端请求是否中断❌ 不会中断,Redis 使用 ASK / MOVED 重定向处理
如何避免丢失数据Redis 使用 MIGRATING + MIGRATE + ASKING 流程精确控制迁移
客户端支持建议使用 Jedis、Lettuce 等自动支持 ASK/MOVED 的客户端

六、如何迁移数据

在 Redis Cluster 中,哈希槽(slot)总共 16384 个,这些槽决定了 key 的分布。假设当前有 4 个主节点,各自持有的槽平均为:

原始槽分布(共 16384 槽):
Node A:0 - 4095
Node B:4096 - 8191
Node C:8192 - 12287
Node D:12288 - 16383

现在新增了一个节点 Node E,我们希望将集群进行重新均衡(reshard),让 5 个节点均分槽位,每个节点应该持有大约:

16384 / 5 = 3276.8 ≈ 3276 ~ 3277 个槽

1. 目标槽位分布(新增后)

我们希望最终槽位分布为:

节点目标槽位范围(近似)数量
Node A0 - 32753276
Node B3276 - 65513276
Node C6552 - 98273276
Node D9828 - 131033276
Node E13104 - 163833280

注意:最后一个节点可以稍多几个槽以补全总数。


2. 哪些节点需要迁移槽位?

我们现在知道 Node E 没有任何槽,它需要接管约 3280 个槽。我们需要从原有的节点 A~D 中按比例迁出一些槽位,例如:

迁出源节点原持有槽数迁出槽数(近似)
Node A4096820
Node B4096820
Node C4096820
Node D4096820

合计:820 * 4 = 3280(正好够给 Node E)


3. 具体迁移哪些槽给 Node E?

我们可以按以下方式进行:

示例划分:

  • Node A 迁出:槽 3276 - 4095(820 个)

  • Node B 迁出:槽 7372 - 8191(820 个)

  • Node C 迁出:槽 11468 - 12287(820 个)

  • Node D 迁出:槽 15564 - 16383(820 个)

Node E 将接管这些槽:

迁入槽总范围:
[3276-4095] + [7372-8191] + [11468-12287] + [15564-16383] = 共 3280 个槽

4. 如何执行迁移?(以 redis-cli 工具为例)

Step 1:添加新节点到集群

redis-cli --cluster add-node <NodeE_IP>:<PORT> <Any_Existing_Node_IP>:<PORT>

Step 2:开始迁移槽(reshard)

redis-cli --cluster reshard <Any_Node_IP>:<PORT>

交互界面示例:

How many slots do you want to move (from existing nodes)? 3280
What is the receiving node ID? <NodeE_ID>
Please enter all source node IDs separated by space: <NodeA_ID> <NodeB_ID> <NodeC_ID> <NodeD_ID>
Do you want to proceed with the proposed reshard plan (yes/no)? yes

工具会自动从每个 source node 迁出约等量的槽,并使用 MIGRATE 将 key 搬至目标节点。


5. 迁移过程中客户端如何处理请求?

  1. Redis 会对迁移中的槽设置状态:

    • 源节点:MIGRATING

    • 目标节点:IMPORTING

  2. 如果客户端访问迁移中的 key,会收到:

    • -ASK slot new_ip:port

  3. 客户端会先发 ASKING,再发原始请求至新节点

  4. 客户端自动更新槽表(Jedis、Lettuce 支持)


6. 迁移结束后的槽位分布

节点最终槽位(示例)数量
Node A0 - 32753276
Node B3276 - 73713276
Node C7372 - 114673276
Node D11468 - 155633276
Node E15564 - 16383 + ...3280

由于每次 slot 分布不能做到完全精确划分,可能最后部分节点多几个槽,不影响功能。


7. 附加建议

  • 大数据量时,使用 --cluster use-empty-masters yes 避免主从冲突

  • 迁移过程中注意磁盘和网络压力,建议 按 slot 分批迁移

  • 如果需要自动脚本迁移,可以用 redis-trib.rb 或封装版的 Python 工具

七、如何处理数据倾斜

在 Redis Cluster 中,数据倾斜是指某些节点上的槽虽然数量看起来一致,但实际承载的数据量明显高于其他节点,造成这些节点成为瓶颈。防止数据倾斜的关键在于

  • 不仅要平均分配槽位(slots),还要确保 key 的哈希分布尽量均匀

  • 避免“热点 key”或“同类 key 前缀”集中落到某一个槽。


1. Redis 数据倾斜的根本原因

数据倾斜 ≠ 槽位不平均

虽然 Redis Cluster 的 key 是通过 CRC16 算法取模映射到 16384 个槽(slot):

slot = CRC16(key) % 16384

但如果 key 的分布不均衡,即使槽均分了,某些节点上的 key 数量或 key 大小也可能暴增。


2. 造成数据倾斜的典型场景

场景描述
热点 key某些 key 的访问频率极高,造成某节点 CPU/内存负载高
key 前缀重复比如 user:1, user:2... 这些 key 落入同一槽
哈希标签不当使用了 {} 包裹部分 key,导致所有 key 落入相同槽(聚簇)
大 key某些 key(如 zset/list/hash)数据量非常大,导致单节点内存暴涨

3. 如何防止数据倾斜(理论 + 实践策略)

✅ 1. 均匀划分槽位

确保每个节点分配大致相同数量的槽(约 16384 ÷ N 个主节点):

redis-cli --cluster reshard --cluster-use-empty-masters yes

但注意:槽均分 ≠ 数据均分,还要关注 key 分布!


✅ 2. 避免热点前缀或哈希标签聚集

❌ 错误示例:
user:{1000}:profile
user:{1000}:settings
user:{1000}:tokens

这些 key 都被哈希到同一个 slot,造成集中。

✅ 正确示例:
user:1000:profile
user:1001:settings
user:1002:tokens

默认采用全 key 参与 CRC16,不使用 {},这样 key 会自然分散。

✅ 更高级的写法:

你可以做简单的 hash 前缀打散:

slot_prefix = CRC16(userId) % 16384
key = "prefix:" + slot_prefix + ":user:" + userId

这样即便 userId 连续,槽也会打散。


✅ 3. 设计时进行 key 分布采样分析

Redis 官方命令或脚本工具:

# 按 slot 采样 key 分布情况
redis-cli --scan | while read key; doslot=$(redis-cli cluster keyslot "$key")echo "$slot" >> slot_dist.txt
donesort slot_dist.txt | uniq -c | sort -nr | head

可视化输出哪些槽过于拥挤,可以进行迁移调整。


✅ 4. 结合 key 大小和访问频率做冷热均衡迁移

有些槽虽然 key 数不多,但 key 太大或太频繁访问。你可以使用如下方式探查:

  • 使用 MEMORY USAGE key 检查 key 大小

  • 使用 MONITORSLOWLOG 或代理层(如 Codis/Twemproxy)做热点分析

  • 对热点 key 分布的槽做调整,把它们拆分出去


✅ 5. 使用自动热点检测与迁移工具

工具方案如:

工具说明
redis-trib.rb / redis-cli --cluster提供 slot 的迁移,但不分析热点
Redis Shake、KeyHub、Codis支持 key 扫描和热点统计分析
自研脚本基于 --scan + cluster keyslot + MEMORY USAGE 做 key 分布和大小采样

4. 动态调优流程建议

  1. 部署前: 预生成 key 示例,使用脚本 hash 计算槽位分布评估是否均匀

  2. 部署后: 定期运行 key 采样分析,观察槽位中 key 总量是否平衡

  3. 运行中: 若出现访问慢、内存飙升、CPU 局部高,结合 MONITOR + key 分布分析

  4. 迁移方案:

    • 如果槽位数量均衡,但数据不均 → 分析热点槽,执行局部槽位迁移

    • 如果槽位数量不均 → redis-cli --cluster reshard 手动或脚本重新均衡


示例脚本:检测槽位中 key 数量分布

# 遍历集群 key,计算每个 slot key 数
redis-cli --scan | while read key; doslot=$(redis-cli cluster keyslot "$key")echo "$slot" >> slots.txt
donesort slots.txt | uniq -c | sort -nr | head -20

输出示例:

800 12536
780 8732
779 8756
...

说明:槽位 12536 中有 800 个 key,可能需要迁出部分 key 给空闲槽位。


5. 总结

策略说明
均分槽位保证基础分布一致性(每节点约 3276 个槽)
Key 命名优化避免使用 hash tag 聚簇 key;避免热点前缀
热点检测通过 key 扫描、访问频率分析检测热点槽
数据大小平衡使用 MEMORY USAGE 对 key 大小做统计
热点迁移对热点槽使用 CLUSTER SETSLOT + MIGRATE 做局部缓解

八、Redis Cluster手动指定槽位(slot)

在 Redis Cluster 中,槽位(slot)总数固定为 16384(编号为 0 ~ 16383),这是 Redis Cluster 的核心机制之一。所有的 key 都通过 CRC16 哈希映射到这些槽位中:

slot = CRC16(key) % 16384

你不能“创建”或“使用”第 16384 以上的槽位——超出范围的槽位是非法的,Redis 会直接报错。


1. 如何“指定”某个槽位?

你不能直接指定槽位编号来存 key,但你可以通过特定 key 设计来确保 key 落入你期望的槽位。有两种方式:


✅ 方法一:使用 Hash Tag {} 来固定槽位

Redis Cluster 中,只有 {} 中的内容会参与哈希计算。

示例:
SET user:{1000}:name "Alice"
SET user:{1000}:email "alice@example.com"

这两个 key 都会被映射到:

slot = CRC16("1000") % 16384

所以,它们会被强制路由到同一个槽位(Cluster 的同一节点)。

⚠️ 这是 Redis Cluster 支持“跨 key 操作”的唯一机制,比如 MGET key1 key2 仅在 key1、key2 落在相同槽位时才可执行。


✅ 方法二:根据 CRC16 手动反查槽位 key

你可以先计算你想要的 slot,然后构造一个 key 让它哈希落到你指定的槽位。

例如你想让一个 key 落到 slot 9999,你可以使用工具来生成这样的 key:

import crcmodcrc16 = crcmod.predefined.mkCrcFun('crc-16')
for i in range(1000000):key = f"key{i}"slot = crc16(key.encode()) % 16384if slot == 9999:print(f"Key: {key} => Slot: {slot}")break

2. 如果你试图使用 >16383 的槽位会怎样?

Redis 明确限制槽位范围:

slot ∈ [0, 16383]

如果你通过 CLUSTER SETSLOTCLUSTER GETKEYSINSLOTCLUSTER ADDSLOTS 等命令尝试使用非法槽位,比如 20000,会报错:

127.0.0.1:7000> CLUSTER SETSLOT 20000 NODE <node_id>
(error) ERR Invalid slot

或:

127.0.0.1:7000> CLUSTER GETKEYSINSLOT 20000 10
(error) ERR Invalid slot

这是 Redis Cluster 源码中硬编码的上限,无法突破。


3. 辅助工具:如何查询 key 的槽位

1️⃣ 查看某个 key 的槽位:

redis-cli -c cluster keyslot yourkey

示例:

> cluster keyslot "user:{1000}:email"
(integer) 5792

2️⃣ 查看每个节点持有哪些槽:

redis-cli -c cluster slots

4. Redis 为何固定 16384 个槽?

  • Redis Cluster 不直接存 key 的映射,而是通过槽位来间接映射;

  • 槽的数量要足够大,以支持灵活迁移、负载均衡;

  • 槽数设为 2 的幂(16384 = 2^14)有利于位运算优化。


5. 总结

问题结论
如何让 key 落在特定槽位?使用 {} 包裹部分 key,或手动计算 CRC16
槽位最大是多少?固定为 0 ~ 16383,共 16384 个
使用超出槽位会怎样?Redis 返回 ERR Invalid slot,操作失败
如何避免冲突和错位?统一规范 key 的 hash tag 使用;槽位映射合理

九、在Redis集群环境中实现Redlock分布式锁

在Redis集群环境中实现Redlock(分布式锁算法),需要遵循Redlock算法的核心思想:在多个独立的Redis节点上获取大多数节点的锁,以确保高可用和正确性。Redis Cluster 自身并不天然支持 Redlock 的所有机制,因此通常是将多个独立的 Redis 实例部署为 Redlock 节点,而不是使用 Redis Cluster 的分片架构。


1. Redlock算法简介

Redlock 是由 Redis 作者 antirez(Salvatore Sanfilippo)提出的一个 分布式锁算法,用于确保在分布式系统中安全可靠地加锁。其核心流程如下:

1. 客户端获取当前时间(毫秒精度)
2. 依次向 N 个独立 Redis 实例写入锁(使用相同的 key 和随机值),设置过期时间 TTL
3. 若能在 T 毫秒内拿到多数(N/2+1)个锁,则认为加锁成功
4. 若失败,释放已获得的锁,重试
5. 解锁时,校验 value(避免误删其他客户端的锁),再删除 key

2. 在 Redis 集群中实现 Redlock 的挑战

Redis Cluster(集群)是基于分片的集群系统,它的节点之间不是独立的,因此不能直接用来实现 Redlock 的“多个独立 Redis 实例”的要求。

Redlock 要求 vs Redis Cluster 特性对比:
要求Redis Cluster 支持情况
多个完全独立 Redis 实例不支持(Redis Cluster 节点间通信)
跨节点一致性加锁不支持(单 key 属于单个节点)
多节点同时写入锁需要额外逻辑或客户端支持

因此,Redlock 更适合部署在多个独立 Redis 实例上,而不是 Redis Cluster 中


3. 正确的 Redlock 部署方式(推荐方案)

部署 5 个完全独立的 Redis 实例(不在同一个物理机,网络独立性较好),然后在应用层通过 Redlock 算法加锁。例如:

Redis1: 10.0.0.1:6379
Redis2: 10.0.0.2:6379
Redis3: 10.0.0.3:6379
Redis4: 10.0.0.4:6379
Redis5: 10.0.0.5:6379

使用 Redlock 客户端库,如:

  • Java: Redisson

  • Python: redis-py + redis.lock or redlock-py

  • Node.js: node-redlock

4. 在 Redis Cluster 中实现分布式锁的建议

虽然不能直接实现 Redlock,但如果你只使用 Redis Cluster(没有独立 Redis 实例),可使用:

  1. 单 key 加锁:Redis Cluster 会将 key 映射到对应节点,只保证该节点的一致性,适合非关键锁。

  2. 通过哈希标签强制同 slot key:如 {lock_key},让多个 key 保持在一个节点,简化锁操作。

  3. 搭配 ZooKeeper / etcd 等实现更高级别的分布式锁机制。


5. 总结

场景是否适合 Redlock
多个独立 Redis 实例是,推荐
Redis Sentinel 模式是,可做 Redlock 节点
Redis Cluster(分片)否,不能直接用作 Redlock

如使用 Redis Cluster,建议采用本地锁+幂等性+补偿机制的混合方案,而不是强行实现 Redlock。

十、使用 Redisson 实现 RedLock 分布式锁

1. Redisson 中 RedLock 实现方式

Redisson 提供了 RedissonRedLock 类来封装这一机制,使用非常简单。

✅ 示例代码:

// 连接5个独立的Redis节点(必须是独立的实例,不是同一个Redis的多个db)
RedissonClient redisson1 = Redisson.create(config1);
RedissonClient redisson2 = Redisson.create(config2);
RedissonClient redisson3 = Redisson.create(config3);
RedissonClient redisson4 = Redisson.create(config4);
RedissonClient redisson5 = Redisson.create(config5);// 获取每个节点的锁
RLock lock1 = redisson1.getLock("my-lock");
RLock lock2 = redisson2.getLock("my-lock");
RLock lock3 = redisson3.getLock("my-lock");
RLock lock4 = redisson4.getLock("my-lock");
RLock lock5 = redisson5.getLock("my-lock");// 构造 RedLock(至少需要 3 个锁成功)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);// 尝试加锁:最多等待 2 秒,锁自动释放时间 10 秒
boolean locked = redLock.tryLock(2, 10, TimeUnit.SECONDS);if (locked) {try {// 执行业务逻辑} finally {redLock.unlock(); // 自动释放全部节点锁}
}

2. RedLock 加锁源码逻辑剖析

核心类:RedissonRedLock

public class RedissonRedLock extends RedissonMultiLock {@Overridepublic boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {// 所有子锁同时尝试获取// 获取超过半数则返回成功}@Overridepublic void unlock() {// 释放所有子锁}
}

加锁成功的判断逻辑:

  • 默认 5 个 Redis 实例时,至少 3 个成功加锁(过半)

  • 每个子锁都使用 tryLock(waitTime, leaseTime),其中 waitTime 是加锁等待总时间

  • 若任何一个子锁返回失败,会立即放弃加锁,释放已获得的锁


3. Redisson 配置方式示例

Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

或连接多个 Redis:

Config config = new Config();
config.useClusterServers().addNodeAddress("redis://127.0.0.1:7001", "redis://127.0.0.1:7002");

⚠️ 注意

  • RedLock 必须使用多个 Redis 实例(建议部署在不同机房/节点)

  • 多 Redis 节点 不能是单机多个 DB,那不符合分布式锁容错性设计


4. 使用 RedLock 的注意事项

注意点说明
Redis 实例独立性要求多个 Redis 物理隔离,部署在不同主机
网络延迟处理Redisson 内部处理了 tryLock 的时间预算与租约计算
少数节点失败容忍支持小部分节点宕机,保证超过半数成功即可
多实例 RedissonClient每个 Redis 实例都需创建独立 RedissonClient

5. RedLock 与普通锁对比

特性单节点 Redis 锁RedLock(多节点)
可用性Redis 挂掉锁失效容忍部分节点失败
安全性主从切换可能导致锁丢失多节点一致性加锁
复杂性实现简单配置和资源较复杂
推荐场景单机开发或容忍少量锁失效关键业务锁、分布式系统中高一致性要求

6. 总结一句话

Redisson 的 RedLock 是对 Redis 官方分布式锁算法的完整实现,适合跨多 Redis 实例、高可用、高一致性的分布式系统中使用,需正确配置多个独立的 Redis 节点才能发挥其真正价值。

相关文章:

  • 【IEEE/EI/Scopus检索】2025年第六届模式识别与数据挖掘国际会议 (PRDM 2025)
  • ros导航原理
  • 如何在 Visual Studio Code 中配置SSH、Git 和 Copilot插件
  • 对象注入 BeanFactory 的操作 BeanFactoryPostProcessor , Spring boot
  • 20250613在Ubuntu20.04.6下编译Rockchip的RK3576原厂Android14的SDK【整理编译】
  • JVM GC 问题排查实战案例
  • CSS flex-basis 属性详解:功能、用法与最佳实践
  • EFK架构的数据安全性
  • 前端性能优化:打造极致用户体验
  • 玩转Docker | 使用Docker部署vaultwarden密码管理器
  • 流编辑器sed
  • Rust编写Shop管理系统
  • 如何有效开展冒烟测试
  • 【redis——缓存击穿】
  • 中国老年健康调查(CLHLS)数据挖掘教程(1)--CLHLS简介和数据下载
  • 【计算机系统结构】期末复习
  • 如何确保邮件群发不会被标记为垃圾邮件?
  • 输入法,开头输入这U I V 三个字母会不显示 任何中文
  • 深入解析 SNMP Walk 的响应机制
  • NaluCFD 介绍和使用指南
  • 越秀区营销型网站建设/谷歌seo技巧
  • 做网站的 深圳/新网站排名优化怎么做
  • 新加坡 网站建设/广东互联网网络营销推广
  • 长沙最大的广告公司/seo顾问阿亮
  • 平易云 网站建设/免费的自媒体一键发布平台
  • 网站返回404是什么意思/网络营销软件代理