单实例 Redis 的两个短板

Redis 性能极高,但单实例有两个绕不开的短板:容量受单机内存限制、单点故障意味着缓存整体不可用。数据涨到几十个 G、或读并发冲到几万时,一台机器既存不下也扛不住;更要命的是,这台机器一旦宕机,所有依赖缓存的接口全部回源数据库,可能直接把库压垮。

解决思路分两步:先解决“挂了怎么办”,用主从加哨兵做高可用;再解决“太大怎么办”,用 Cluster 把数据分片到多台机器。理解了这两层,Redis 的生产部署才算入门。

本文核心知识点
  • 主从复制写主库、读从库,分担读压力并做热备。
  • 哨兵独立进程监控主从,主库挂了自动把从库提为主库。
  • Cluster把数据按槽分散到多个主节点,水平扩容。
  • 槽位16384 个哈希槽,key 落在哪个槽就路由到哪个节点。
Redis 集群配图:蓝色渐变背景下方一圈六个数据库圆柱节点由虚线发光连线组成环形,上方两个监控摄像头形状的哨兵节点注视整圈,扁平科技插画
下方一圈主从节点组成数据环,上方哨兵节点负责健康检查与故障切换。

主从复制:读写分离

最简单的高可用形态是一主多从:主库负责写,从库实时同步主库的数据、负责读。复制的原理是主库把写操作以命令流的形式持续发给从库,从库重放这些命令,保持数据一致。读多写少的场景(缓存、排行榜)用读写分离,读压力就被从库摊掉了。

但主从只是“数据副本”,主库挂了并不会自动有从库顶上——客户端还在往已经死掉的主库写。这时候要么人工介入切主,要么引入哨兵自动完成。另外复制是异步的,主库写成功就返回,还没同步到从库就宕机,会丢一小段数据,对一致性要求极高的场景要心里有数。

哨兵 Sentinel:自动故障切换

哨兵是一组独立运行的监控进程,它做三件事:定期心跳探测主库和从库是否存活、多数哨兵都认为主库下线时触发客观下线、从从库里挑一个最合适的提升为新主库并让其余从库改跟新主。

要点:哨兵本身也要至少部署 3 个奇数节点,形成多数派投票,否则单个哨兵误判就会乱切主。客户端通过哨兵发现当前的主库地址,主从切换后哨兵会通知客户端新主库在哪。这样主库宕机时,几十秒内自动完成切换,业务基本无感。

为什么还要分片

主从加哨兵解决了高可用,但所有数据还是只存在主库这一台机器的内存里。数据量超过单机内存、或写并发超过单机网卡与 CPU 能力时,再多加从库也没用——写还是只能落在主库。这时候需要的是把数据本身切开,让不同的 key 落在不同的主节点上,这就是分片。

Redis 官方的分片方案就是 Redis Cluster。它把“分片 + 高可用”合在一起:每个主节点负责一部分数据,每个主节点再挂一个从节点做备份,主节点挂了它的从节点自动顶上,不再需要独立的哨兵进程。

Cluster 与 16384 个槽

Redis Cluster 没有用取模,而是引入了哈希槽这个中间层:总共 16384 个槽,每个 key 用 CRC16(key) mod 16384 算出自己该落在哪个槽,再由集群把这些槽平均分配给各个主节点。比如 3 个主节点,就分别负责 0 到 5460、5461 到 10922、10923 到 16383 这三段槽。

# key 与槽的对应:16384 个槽,按节点分段
slot = CRC16("user:1001") mod 16384
# 节点 A 管 0..5460,节点 B 管 5461..10922,节点 C 管 10923..16383

为什么是槽而不是直接对节点取模?因为槽是可以迁移的。扩容加节点时,不用像取模那样把所有 key 重算分布,只需要把某些槽从老节点挪到新节点即可,老 key 的归属在迁移过程中平滑变化。这是 Cluster 能在线扩容的关键。

槽位路由与重定向

客户端连进集群时,会拿到一份槽位与节点的映射表,本地记录“哪个槽在哪个节点”。请求一个 key 时,客户端自己算槽、找节点、直发过去。如果槽已经迁走、或客户端的映射表过期了,节点会返回一个 MOVED 重定向,告诉客户端这个槽现在在哪个节点,客户端据此更新本地映射再重试。

要注意多键操作的限制:同一个事务、Lua 脚本、mget 里的多个 key 必须落在同一个槽,否则集群报错。需要让多个相关 key 同槽时,用哈希标签 {user:1001}:profile 这种花括号语法——只对花括号内的部分算槽,这样同一用户的多个 key 就被绑在同一槽。