在前面的文章中,我们聊了 Redis 的持久化、锁机制以及热 Key 问题。今天,我们跳出具体的命令细节,从宏观架构的角度来看看 Redis 是如何一步步“做大做强”的。
在生产环境中,我们几乎不会只部署一台 Redis。为什么?因为单机 Redis (Single Node)存在三个无法回避的致命伤:
单点故障 (SPOF):万一机器宕机,全村吃饭。
容量瓶颈:一台机器的内存终究有限,存不下海量数据。
性能瓶颈:QPS 再高也受限于单机的 CPU 和网卡带宽。
为了解决这些问题,Redis 的架构经历了从主从复制到哨兵模式,再到分片集群的进化之路。
前置知识:什么是高可用?
简单来说,redis单节点存在比如性能瓶颈、宕机风险等问题,可用性低,那高可用就是解决这个单节点低可用的问题。
一、 第一阶段:主从复制 (Master-Slave) —— 解决“备份与分流”
为了防止“单点故障”,最直接的思路就是:找个替补。
这就是主从架构的初衷。我们部署多台 Redis,一台做主节点 (Master),几台做从节点 (Slave)。
1. 核心架构
Master:唯一的“写”入口。负责接收写请求,并将数据异步同步给 Slave。
Slave:只读副本。负责接收读请求,并被动同步 Master 的数据。
2. 为什么要读写分离?
这源于一个业务常识:在绝大多数互联网场景中,读操作远远多于写操作(比如微博,看的人多,发的人少)。
如果不分离,所有压力都在 Master 身上。
分离后,Master 专注处理那 10% 的写请求,N 个 Slave 分担那 90% 的读请求。这样既提高了并发量,又避免了写操作(如持久化)阻塞读请求。
3. 依然存在的问题
主从架构解决了**“数据备份”和“读性能”**问题,但它有一个致命的缺陷:不够智能。
如果 Master 突然宕机,Slave 不会自动变成 Master。运维人员必须半夜爬起来,手动修改配置把 Slave 提拔上来。在人工介入的这段时间,服务是不可写的。
二、 第二阶段:哨兵模式 (Sentinel) —— 解决“自动故障恢复”
为了解决主从架构“人工恢复慢”的问题,Redis 引入了 哨兵 (Sentinel) 机制。
你可以把哨兵理解为一群**“自动化的运维巡逻队”**。它们不存数据,只干三件事:监控、选主、通知。
1. 哨兵的三大职责
监控 (Monitoring):哨兵集群通过心跳机制(Ping),每秒检查 Master 和 Slave 是否活得好好的。
主观下线:一个哨兵觉得 Master 没回消息。
客观下线:超过半数(Quorum)哨兵都觉得 Master 没回消息,那才是真挂了。
自动故障恢复 (Failover):当 Master 挂了,哨兵会自动从剩下的 Slave 中选出一个新的 Master。
通知 (Notification):哨兵充当了服务发现的角色。客户端连接的是哨兵,当 Master 换人了,哨兵会把新地址推给客户端。
2. 新 Master 是怎么选出来的?
当 Master 倒下,哨兵会按照以下规则依次筛选 Slave:
排除法:先把那些断开连接很久、不健康的 Slave 踢出局。
拼优先级:看
slave-priority配置,数字越小优先级越高(0 表示永不当选)。拼进度 (Offset):谁的数据最全(Offset 最大),谁就当选。这意味着它和旧 Master 的数据差异最小。
拼人品 (RunID):如果以上都一样,就按运行 ID 排序,选 ID 小的。
3. 依然存在的问题
哨兵模式实现了高可用 (High Availability),但它依然是**“主从模式”**的变种。
写瓶颈:依然只有一个 Master 负责写,无法应对高并发写请求。
容量瓶颈:所有节点存的数据都是一样的,无法突破单机内存上限(比如你只有 64G 内存,就存不下 100G 数据)。 解释:(比如:我有 3 台机器,每台 64G,加在一起我就能存 $64 \times 3 = 192G$ 的数据。但在 Redis 主从/哨兵架构下,这是错的!你依然只能存 64G。Master 有什么,Slave 就必须有一份一模一样的。)
三、 第三阶段:分片集群 (Cluster) —— 解决“海量数据与高并发写”
为了解决“存不下”和“写不过来”的问题,Redis 3.0 推出了 Redis Cluster。
这是真正的分布式去中心化方案,实现了多主多从。
1. 核心原理:散列插槽 (Hash Slots)(见下文解释)
Redis Cluster 没有把数据直接绑定到节点上,而是引入了一个概念:插槽 (Slot)。
整个集群被逻辑切分为16384个插槽。
数据绑定插槽:当存一个 Key 时,Cluster 会根据
CRC16(key) % 16384计算出这个 Key 属于哪个插槽。插槽绑定节点:这 16384 个插槽被均匀分配给集群中的多个 Master 节点。
2. 为什么要这么设计?
解耦:数据跟节点没关系,只跟插槽有关系。
易扩容:如果你想增加一个 Master 节点,只需要从其他节点挪一些“插槽”给新节点即可。数据迁移时,通过插槽搬运非常方便,不需要全量重新哈希。
3. Cluster 的能力
多主写入:集群中有多个 Master,每个 Master 负责一部分插槽。这意味着写请求被分散了,性能线性提升。
高可用:每个 Master 都有自己的 Slave。如果某个 Master 挂了,它的 Slave 会自动顶上(集群内部实现了类似哨兵的选举机制)。
智能路由:客户端请求任意一个节点,如果数据不在该节点,它会返回重定向错误,告诉客户端去哪个节点找。
附:对于插槽——全量哈希的优化以及解耦思想的理解:
1. 如果没有插槽:直接哈希 (The Old Way)
假设你是一个物流主管,你有 3 辆车 (Node A, B, C),你要运送 10 万个包裹 (Keys)。
最笨的办法(直接绑定节点):
规则:拿到包裹,看一下单号,除以 3 取余数。
余数 0 -> 扔进 A 车。
余数 1 -> 扔进 B 车。
余数 2 -> 扔进 C 车。
灾难发生了:扩容
生意太好,你买了一辆新车 D 车(节点增加到 4 个)。 规则变了:现在要除以 4 取余数。
后果:
原来的包裹单号是 3,3 % 3 = 0 (在 A 车)。
现在的包裹单号是 3,3 % 4 = 3 (得去 D 车)。
你会发现,几乎所有的包裹都需要从原来的车里翻出来,重新分配到新车去。 这就叫“全量重新哈希”。在数据迁移期间,你的物流系统基本瘫痪了。
2. 有了插槽:引入“中间层” (The Slot Way)
Redis Cluster 说:“别把包裹直接扔车里!先把包裹装进统一的箱子里!” 这 16384 个插槽,就是 16384 个固定的塑料收纳箱。
步骤一:包裹进箱子(这一步永远不变)
不管你有几辆车,我的规则是铁打不动的: 包裹 (Key) -> 经过 CRC16 算法 -> 算出它属于 第 500 号箱子 (Slot 500)。
重点:这个包裹永远属于第 500 号箱子,天塌下来它也在这个箱子里。
步骤二:箱子上车(这一步灵活多变)
现在你有 3 辆车 (Node A, B, C)。 你只需要分配箱子就行了:
A 车:负责运送 0 ~ 5000 号箱子。
B 车:负责运送 5001 ~ 10000 号箱子。
C 车:负责运送 10001 ~ 16383 号箱子。
3. 为什么这样扩容就容易了?
现在,生意好了,你又买了一辆新车 D 车。 这就是插槽的魔力时刻:
不需要动包裹:你不需要去把每一个包裹拿出来重新算。包裹依然在它原来的箱子里。
只需要挪箱子: 你对 A 车说:“把你那里的 0~1000 号箱子搬给 D 车。” 你对 B 车说:“把你那里的 5001~6000 号箱子搬给 D 车。” 你对 C 车说:“把你那里的 10001~11000 号箱子搬给 D 车。”
结果:
A、B、C 车腾出了一点空间。
D 车也有活干了。
整个过程只是搬运了几个箱子,而不是把几十万个散落的包裹重新分拣。
4. 总结:如何理解“解耦”?
插槽就是一个**“中间商”**。
没有插槽时:数据 <-> 节点(直接强绑定,节点变了,数据位置全乱)。
有插槽时:
数据 <-> 插槽(强绑定,由于插槽数量 16384 是固定的,所以这个关系永远不变)。
插槽 <-> 节点(弱绑定,可以随时把插槽从这个节点挪到那个节点)。
一句话总结:插槽就像是搬家时的**“打包箱”**。 如果没有打包箱,搬家时你要一双袜子、一本书地搬(数据迁移极慢)。 有了打包箱,你只需要把箱子搬上车,至于哪个箱子在哪个车上,随时可以调整。这就是 Redis Cluster 灵活扩容的秘密。
四、 总结与选型
我们将这三个架构做一个最终对比:
| 架构模式 | 核心特点 | 解决的问题 | 遗留的问题 | 适用场景 |
| 主从复制 | 一主多从,读写分离 | 单点故障、读性能瓶颈 | 故障恢复需人工、单机容量限制 | 读多写少、数据量小、允许短暂宕机 |
| 哨兵模式 | 主从 + 自动监控 | 自动故障恢复 (HA) | 依然是单主写入、单机容量限制 | 读多写少、对可用性要求高 |
| 分片集群 | 多主多从,数据分片 | 海量数据存储、高并发写 | 架构复杂、不支持部分跨 Slot 操作 | 海量数据、高并发写、核心业务 |
一句话总结:
如果你的 Redis 只是为了做缓存且数据量不大,主从就够了。
如果你不能忍受机器挂了要半夜起来修,那就上哨兵。
如果你有100G 以上的数据或者每秒 10 万次的写请求,请毫不犹豫地使用Redis Cluster。
最后说一下我自己对于扩容的理解:
我觉得容量问题的解决可以从两个维度去理解,第一个是多主多从这多主之间并不是和主从一样是复制备份的关系,另外引入插槽的概念它优化了扩容带来的全量哈希的危害。所以集群架构解决了容量问题。