news 2026/6/13 21:38:59

Redis 从入门到精通:分片之道 —— Redis Cluster

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis 从入门到精通:分片之道 —— Redis Cluster

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

通过主从复制和 Sentinel 哨兵,我们解决了数据冗余、读写分离和自动故障转移。但所有这些架构中,写入操作始终只能由一台主节点承担。当数据量超过单机内存,或写入 QPS 突破单机瓶颈时,纵向扩展(升级更大内存、更强 CPU)终有尽头。

Redis Cluster就是突破这一物理限制的终极方案。它通过哈希槽将数据分散到多个主节点上,让写入能力可以随着节点增加而线性扩展。本文将从原理出发,用 Docker 搭建一个 6 节点集群,深入 MOVED/ASK 重定向机制,并让 Python 客户端像操作单机一样操作集群。

1. Redis Cluster 是什么?为什么需要它?

Redis Cluster 是 Redis 官方提供的去中心化分片方案,每个节点只存储一部分数据,所有节点共同组成一个逻辑上的“大数据池”。它具有三个核心能力:

  • 数据分片:通过哈希槽将键自动分布到不同主节点,突破单机内存和写入瓶颈。

  • 高可用:每个主节点可配置从节点,主节点故障时自动进行故障转移。

  • 去中心化:节点之间通过 Gossip 协议交换状态信息,无需第三方协调者(如 Sentinel)。

┌─────────────┬─────────────┬─────────────┐ │ Master1│ Master2│ Master3│ │ 槽0-5460 │ 槽5461-10922│ 槽10923-16383│ ├─────────────┼─────────────┼─────────────┤ │ Slave1│ Slave2│ Slave3│ └─────────────┴─────────────┴─────────────┘

💡 官方推荐至少3 主 3 从,这是生产集群的最小配置。

2. 核心原理:哈希槽(Hash Slot)

Redis Cluster 没有使用一致性哈希,而是采用了哈希槽。总计16384个槽,每个主节点负责一部分槽。

键到槽的映射

HASH_SLOT=CRC16(key)%16384

CRC16会对键名计算一个 16 位校验和,然后对 16384 取模,结果在 0~16383 之间。集群中的每个主节点管理一段连续的哈希槽。

为什么是 16384?

  • 16384 个槽足够在最多 1000 个节点间均匀分配(每个节点 16 个槽)。

  • 槽信息通过 Gossip 协议在节点间传播,16384 个槽的位图只需要 2KB,开销可控。

  • 心跳包中可以轻松携带完整的槽分配信息。

Hash Tag
有时我们希望多个键(如user:1001:profileuser:1001:score)落在同一个槽。可以通过{...}指定参与哈希计算的部分:

HASH_SLOT=CRC16("1001")%16384# 只对 {} 内的部分计算

例如:user:{1001}:profileuser:{1001}:score一定会落在同一节点,方便批量操作和事务。

3. 实战:Docker 搭建 6 节点集群

我们用 Docker 在一台机器上搭建 3 主 3 从的集群(仅演示,生产应分布在不同机器)。

3.1 创建网络和 6 个节点

dockernetwork create cluster-net# 创建 6 个节点(端口 6371~6376)forportin$(seq63716376);dodockerrun-d\--nameredis-cluster-${port}\--networkcluster-net\-p${port}:6379\redis:7.2 redis-server\--cluster-enabledyes\--cluster-config-file nodes.conf\--cluster-node-timeout5000\--appendonlyyes\--appendfsynceverysecdone

参数说明:

  • cluster-enabled yes:开启集群模式。

  • cluster-config-file nodes.conf:节点自动保存集群拓扑的文件。

  • cluster-node-timeout 5000:节点超时判定时间(毫秒)。

3.2 初始化集群

Redis 5.0+ 提供了redis-cli --cluster create命令,一行完成初始化:

# 获取容器 IP(在同一 Docker 网络内使用容器名即可)# 或者进入任意容器用 redis-cli 操作dockerexec-itredis-cluster-6371bash# 在容器内执行(容器名就是 hostname)redis-cli--clustercreate\redis-cluster-6371:6379\redis-cluster-6372:6379\redis-cluster-6373:6379\redis-cluster-6374:6379\redis-cluster-6375:6379\redis-cluster-6376:6379\--cluster-replicas1

--cluster-replicas 1表示为每个主节点分配 1 个从节点。

交互过程输出:

>>>Performinghashslots allocation on6nodes... Master[0]->Slots0-5460Master[1]->Slots5461-10922Master[2]->Slots10923-16383Adding replica redis-cluster-6375:6379 to redis-cluster-6371:6379 Adding replica redis-cluster-6376:6379 to redis-cluster-6372:6379 Adding replica redis-cluster-6374:6379 to redis-cluster-6373:6379>>>Trying to optimize slaves allocationforanti-affinity[OK]All16384slots covered.

输入yes确认后,集群搭建完成。

3.3 验证集群状态

dockerexec-itredis-cluster-6371 redis-cli-ccluster nodes

输出示例:

a1b2c3...172.18.0.2:6379@16379 myself,master -017181000000001connected0-5460 d4e5f6...172.18.0.3:6379@16379 master -017181000010002connected5461-10922 g7h8i9...172.18.0.4:6379@16379 master -017181000020003connected10923-16383 j0k1l2...172.18.0.5:6379@16379 slave a1b2c3...017181000030001connected m3n4o5...172.18.0.6:6379@16379 slave d4e5f6...017181000040002connected p6q7r8...172.18.0.7:6379@16379 slave g7h8i9...017181000050003connected

可以看到 3 个master各自负责一段槽,每个 master 有一个slave

dockerexec-itredis-cluster-6371 redis-cli-ccluster info# cluster_state:ok# cluster_slots_assigned:16384# cluster_slots_ok:16384# cluster_known_nodes:6# cluster_size:3

4. 重定向:MOVED 与 ASK

客户端向任意节点发送命令,如果键不在该节点,会发生什么?

4.1 MOVED 重定向(永久)

# 连接到节点 6371dockerexec-itredis-cluster-6371 redis-cli-c# 设置一个键,可能不在本节点127.0.0.1:6379>SET test:1"hello"

如果test:1不在当前节点,-c模式会自动跟随重定向,输出类似:

->Redirected to slot[13643]located at172.18.0.4:6379 OK

不带-c的客户端会收到错误:

127.0.0.1:6379>SET test:1"hello"(error)MOVED13643172.18.0.4:6379

MOVED错误包含了正确节点的 IP 和端口,客户端应更新本地槽映射并重新向正确节点发送命令。

4.2 ASK 重定向(临时)

当集群进行槽迁移时(例如扩容缩容期间),部分键可能暂时存在于两个节点。此时如果访问迁移中的槽,客户端可能收到ASK重定向:

127.0.0.1:6379>GET migrating_key(error)ASK13643172.18.0.5:6379

ASKMOVED的区别:

  • MOVED:槽的所有权已经转移,客户端应永久更新槽映射。

  • ASK:只是临时重定向,下一次请求可能还在原节点,客户端不应更新槽映射。处理时需先发送ASKING命令。

4.3 Smart Client 如何工作

手动处理MOVED/ASK太麻烦,所以有了Smart Client(智能客户端)。redis-pyRedisCluster类会:

  1. 启动时通过CLUSTER SLOTS命令获取槽分布。

  2. 本地缓存槽到节点的映射。

  3. 收到MOVED后自动更新映射并重试。

  4. 收到ASK后自动发送ASKING并重试。

对开发者来说,几乎感觉不到集群的存在。

5. Python 访问 Redis Cluster

5.1 安装与连接

from redis.clusterimportRedisCluster# 连接集群(只需提供部分节点,客户端会自动发现全部)cluster=RedisCluster(host='localhost',port=6371,decode_responses=True,# 或者直接传入多个启动节点# startup_nodes=[# {'host': 'localhost', 'port': 6371},# {'host': 'localhost', 'port': 6372},# ])# Pingprint(cluster.ping())# True

5.2 基础读写

# 写入cluster.set('user:1001','Alice')cluster.set('user:2002','Bob')cluster.set('product:5001','iPhone')# 读取print(cluster.get('user:1001'))# Aliceprint(cluster.get('user:2002'))# Bobprint(cluster.get('product:5001'))# iPhone# 批量操作(需要 hash tag 保证同一槽)cluster.mset({'user:{1001}:name':'Alice','user:{1001}:age':'30'})print(cluster.mget(['user:{1001}:name','user:{1001}:age']))# ['Alice', '30']# 键不存在返回 Noneprint(cluster.get('nonexistent'))# None

5.3 使用 Hash Tag 实现跨键原子操作

由于集群中事务和 Lua 脚本只能操作同一槽中的键,我们需要使用 Hash Tag:

# 错误的用法:两个键不在同一槽,Lua 脚本会报错# cluster.eval("...", 2, 'user:1001', 'order:1001')# 正确的用法:用 Hash Tag 强制同一槽script="""localuser=redis.call('GET', KEYS[1])localorder=redis.call('GET', KEYS[2])return{user, order}""" result=cluster.eval(script,2,'user:{1001}',# 都包含 {1001}'order:{1001}')print(result)# ['Alice', 'some_order']

5.4 Pipeline 在集群中的使用

集群模式下 Pipeline 默认只能操作同一节点的键。redis-py支持自动分组:

pipe=cluster.pipeline()# 可以写不同槽的键,但 execute 时会自动按节点分组发送pipe.set('key1','value1')pipe.set('key2','value2')pipe.set('key3','value3')pipe.get('key1')pipe.get('key2')pipe.get('key3')results=pipe.execute()print(results)# [True, True, True, 'value1', 'value2', 'value3']

⚠️ 跨节点的 Pipeline 性能增益有限(仍然需要向多个节点分别发送),但其原子性也不保证。

5.5 封装集群操作工具类

from redis.clusterimportRedisClusterimportjson class RedisClusterClient:"""Redis Cluster 客户端封装""" def __init__(self, startup_nodes,decode_responses=True): self.client=RedisCluster(startup_nodes=startup_nodes,decode_responses=decode_responses,max_connections=50,retry_on_timeout=True,)def cache_set(self, key, value,ex=3600):"""设置缓存""" data=json.dumps(value,ensure_ascii=False)returnself.client.set(key, data,ex=ex)def cache_get(self, key):"""获取缓存""" data=self.client.get(key)ifdata:returnjson.loads(data)returnNone def atomic_incr_with_limit(self, key, limit,delta=1):"""原子递增并检查上限(使用 Hash Tag 确保落在同一槽)""" script="""localcurrent=redis.call('GET', KEYS[1])ifcurrent==falsethencurrent=0elsecurrent=tonumber(current)endifcurrent + tonumber(ARGV[1])>tonumber(ARGV[2])thenreturn-1endreturnredis.call('INCRBY', KEYS[1], ARGV[1])"""returnself.client.eval(script,1, key, delta, limit)def close(self): self.client.close()# 使用cluster_client=RedisClusterClient(startup_nodes=[{'host':'localhost','port':6371},])cluster_client.cache_set('user:1001',{'name':'IT策士','score':100})print(cluster_client.cache_get('user:1001'))# {'name': 'IT策士', 'score': 100}# 原子限流print(cluster_client.atomic_incr_with_limit('rate:{user123}',5))# 1print(cluster_client.atomic_incr_with_limit('rate:{user123}',5))# 2

5.6 异步 Redis Cluster

importasyncio from redis.asyncio.clusterimportRedisCluster as AsyncRedisCluster async def async_cluster_demo(): client=AsyncRedisCluster(host='localhost',port=6371,decode_responses=True,)await client.set('async_key','Hello Cluster')value=await client.get('async_key')print(value)# Hello Clusterawait client.close()asyncio.run(async_cluster_demo())

6. 集群限制与注意事项

使用集群前必须了解以下约束:

  • 多键操作必须同槽SINTERSUNIONMGETRENAME等操作涉及的所有键必须落在同一槽。使用 Hash Tag 可解决。

  • Lua 脚本同槽限制:脚本访问的键也必须同槽。

  • 事务同槽限制MULTI/EXEC操作的键必须同槽。

  • 跨槽订阅:Pub/Sub 在集群中消息会广播到所有节点,可以在任意节点订阅和发布。

  • Slot 迁移时性能下降:迁移过程中对应槽的请求会有短暂延迟。

7. 动手试试

  1. 搭建集群并测试重定向:用不带-credis-cli向任意节点写入,观察MOVED错误。

  2. Hash Tag 实践:尝试用MSETMGET操作user:{1001}:nameuser:{1001}:age,验证成功。再试一个不带 Hash Tag 的MGET,观察报错。

  3. 故障转移演练:停掉一个主节点(如 6371),观察其从节点自动提升为主,并用 Python 客户端验证读写不受影响。

  4. 扩缩容模拟:用redis-cli --cluster add-node添加新节点,--cluster reshard迁移槽,观察ASK重定向。

预期效果:MOVED 重定向报错正确;Hash Tag 让批量操作成功;主节点宕机后约 5 秒客户端恢复;扩容迁移时产生 ASK 重定向。

8. 总结

本文我们吃透了 Redis 终极形态——Cluster:

  • 哈希槽:16384 个槽,CRC16(key) % 16384决定键落在哪个主节点。

  • 搭建redis-cli --cluster create一行命令创建集群。

  • 重定向MOVED永久、ASK临时,Smart Client 自动处理。

  • Python 操作RedisCluster客户端,支持 Pipeline、Lua(同槽)、异步。

  • 限制:多键操作和脚本需配合 Hash Tag。

至此,Redis 的高可用架构三部曲(主从复制 → Sentinel 哨兵 → Cluster 集群)全部完成。从下一篇开始,我们将切入实战性最强的主题——缓存穿透、击穿、雪崩,用 Redis 解决生产中最头疼的缓存难题。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 21:35:02

I2C总线协议与i.MX23实战:从两线制原理到DMA高效编程

1. I2C总线协议:嵌入式世界的“电话会议”系统如果你玩过嵌入式开发,尤其是单片机或者像i.MX23这样的应用处理器,那你肯定绕不开I2C。这东西就像设备之间开“电话会议”的规则手册。想象一下,在一个电路板上,有十几个“…

作者头像 李华
网站建设 2026/6/13 21:28:24

从PCIe到CXL:深入理解DVSEC如何“告诉”系统你的设备是CXL设备

从PCIe到CXL:系统如何通过DVSEC识别设备协议类型当一台服务器启动时,系统固件会像侦探一样扫描每个PCIe设备,试图揭开它们的真实身份。在这个过程中,一个名为DVSEC的数据结构扮演着关键角色——它决定了设备是继续以传统PCIe身份运…

作者头像 李华
网站建设 2026/6/13 21:22:12

如何快速解锁加密音乐:Unlock Music完整使用指南

如何快速解锁加密音乐:Unlock Music完整使用指南 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地址: https://gi…

作者头像 李华
网站建设 2026/6/13 21:19:53

深入解析i.MX21寄存器映射:从内存映射到外设驱动的底层开发指南

1. 从地址到指令:理解i.MX21寄存器映射的核心逻辑 搞嵌入式开发,尤其是底层驱动和BSP移植,最绕不开的就是芯片的 寄存器映射 。这东西说白了,就是芯片厂商给自家芯片内部所有功能模块(比如DMA、UART、GPIO&#xff0…

作者头像 李华