5. 缓存-Redis-架构

文章目录



一、Redis主从

Redis 主从是指:一个 Redis 实例作为主节点(Master),负责写入。

一个或多个 Redis 实例作为从节点(Slave/Replica),从主节点同步数据并提供读服务。

Redis主从最全详解原理架构流程mikechen

主从复制的核心目的主要有三个:

数据冗余与高可用基础:主节点数据可以复制到多个从节点,降低单点故障风险。

读写分离:写请求集中到主节点,读请求可以分散到从节点,从而提升整体吞吐量。

故障恢复与弹性扩展:当主节点出现问题时,从节点可以在一定机制下接管服务,保证业务连续性。

1. Redis主从架构

Redis主从架构,主要就是两部分:Redis Master(主)和Redis Slave(从)。

Redis主从最全详解原理架构流程mikechen

Application
|
+-------+-------+
||
写读
||
    v               v
+--------++----------+
|Master|---->|Replica1|
+--------++----------+
|+----------+
+--------->|Replica2|
+----------+
|
+--------->+----------+
|Replica3|
+----------+

Master负责写:

SETuser:1001

然后把数据变化复制给多个 Replica。

读请求可以分散到:

  1. Replica1;
  2. Replica2;
  3. Replica3;

这样可以提高 Redis 的读吞吐能力

2. Redis主从原理

Redis 的主从复制流程,如下:

Redis主从最全详解原理架构流程mikechen-mikechen")

  1. 建立复制关系

从节点通过配置或命令指定主节点地址,随后向主节点发起连接请求。

连接建立后,从节点会向主节点发送同步请求。

  1. 全量同步

当从节点首次连接主节点,或者复制断开时间较长、无法通过增量补齐时,主节点会进行全量同步。

全量同步的特点是数据完整,但代价较高,尤其在数据量大时会占用较多 CPU、磁盘和网络资源。

  1. 增量同步

当主从连接短暂中断后,如果主节点仍保留断线期间的复制积压数据。

节点可通过增量同步补齐差异,而无需重新执行全量同步。

增量同步显著降低了复制成本,也是 Redis 主从复制高效的重要原因。

二、Redis 高可用进阶(二):Sentinel 哨兵与选主算法

Redis Sentinel 解决的不是“Redis 永不故障”,而是三个更具体的问题:谁来判断主库不可用,谁有权执行切换,以及应用如何找到切换后的新主库。

这三个问题看似简单,实际横跨故障检测、分布式投票、异步复制、客户端服务发现和云原生网络。生产环境中常见的“Sentinel 明明部署了,为什么仍中断两分钟”,通常不是单点错误,而是控制面、数据面和客户端同时存在缺口。

本文以一个大促商品详情缓存集群为贯穿案例,完整拆解 Sentinel 的内部机制、选主算法、故障转移状态机与工程边界,并给出可落地的 Redis、Sentinel、Spring Boot 和 Kubernetes 配置。

说明:本文使用 Redis 官方现行术语 master/replica;旧版本配置中的 slave-* 已对应为 replica-*。文中的容量数字是案例的设计目标和演练条件,不是未经验证的性能结论。上线前必须用自己的数据结构、命令比例和网络环境压测。


1. 一次大促事故:Redis 切了主,业务却没有恢复

1.1 业务背景

某电商商品域用 Redis 缓存商品详情、营销标签和短时库存展示值:

  • 日常读取约 2 万 QPS,大促设计峰值 12 万 QPS;

  • 读写比约 200:1,单个热销商品可能占总请求的 15%;

  • PostgreSQL 是商品真相源,Redis 数据可以重建;

  • Redis 为一主两副本,3 个 Sentinel,应用部署 60 个 Pod;

  • 目标是单 Redis 节点故障后自动恢复,缓存故障不得拖垮数据库。

零点流量上升后,主库宿主机发生网络故障。Sentinel 最终完成了主从切换,但业务仍持续出现 5xx。事故链路如下:

    1. down-after-milliseconds 设为 30 秒,故障发现本身就不可能在 5 秒内完成;
    1. 一个 Sentinel 与主库部署在同一节点,节点故障后只剩两个 Sentinel,网络抖动期间无法稳定形成多数派;
    1. 一部分应用直连旧主 IP,没有使用 Sentinel-aware 客户端;
    1. 其余应用在切换时立即无退避重试,60 个 Pod 同时重建连接,形成连接风暴;
    1. Redis miss 被不加限制地回源数据库,最终 Redis 故障演变为数据库雪崩。

这次事故暴露出一个核心事实:

Sentinel 只负责 Redis 主从拓扑的故障转移,不负责业务降级、数据库保护、命令幂等,也不会替客户端自动更换一个写死的 IP。

1.2 故障预算应该如何拆

切换时间不是一个 failover-timeout 参数,而是一条端到端链路:

业务不可用窗口 ≈ 故障检测 + ODOWN 协商 + Leader 选举 + Replica 晋升 + 配置传播 + 客户端发现/重连

每一项都受网络 RTT、事件循环阻塞、Sentinel 存活数、Replica 数据新鲜度和客户端行为影响。因此 SLO 应通过故障演练得到分位数,而不能把某个配置值直接写成“保证 5 秒切换”。


2. 先明确边界:Sentinel 能做什么,不能做什么

2.1 四项职责

根据 Redis Sentinel 官方文档,Sentinel 同时承担四类职责:

    1. 监控:周期性检查 master、replica 和其他 Sentinel;
    1. 通知:通过日志、Pub/Sub 或通知脚本输出状态变化;
    1. 自动故障转移:选出执行者,把合适的 replica 提升为 master,并重配其余 replica;
    1. 配置提供者:客户端通过 master name 查询当前 master 地址。

在逻辑上,Sentinel 是控制面,Redis 是数据面,客户端是流量切换执行者:

客户端对 Sentinel 的正确使用方式,不是把 Sentinel 当数据代理。应用先从 Sentinel 获取 master 地址,再直接连接 Redis 数据节点。官方的 Sentinel client specification 要求客户端具备显式 Sentinel 支持。

2.2 Sentinel 不提供的能力

  • 不分片:一个 master group 的写吞吐仍受单 Redis 主线程和单节点资源上限约束;

  • 不提供强一致复制:Redis 主从复制默认异步,故障切换存在丢失最近写入的窗口;

  • 不保证业务写幂等:超时可能发生在服务端执行成功、客户端收到响应之前;

  • 不保护下游数据库:缓存失效后的并发回源必须由应用治理;

  • 不自动修复错误客户端:直连 IP、错误 DNS 缓存、无限重试都在 Sentinel 的职责之外;

  • 不等同于跨地域容灾:跨地域 RTT、分区和复制延迟需要独立的灾备设计。

因此,Sentinel 最适合“数据量能放入单主、以读为主、允许短暂切换窗口、应用支持 Sentinel 服务发现”的场景。


3. Sentinel 如何认识整个拓扑

3.1 为什么只配置 master

最小配置只需声明 master:

sentinel monitor orders-cache redis-0.redis-headless.cache.svc.cluster.local 6379 2

Replica 由 Sentinel 查询 master 的 INFO replication 自动发现。监控同一 master group 的 Sentinel 则通过 Redis 节点上的 __sentinel__:hello Pub/Sub 频道互相发现,并传播自己的地址、run ID 和配置纪元。

这带来两个容易忽略的结论:

    1. Sentinel 不只是要能访问初始 master,还必须能访问 master 报告出来的每个 replica 地址;
    1. NAT、端口映射或错误的容器通告地址会让“看起来已启动”的 Sentinel 找不到可晋升节点。

3.2 健康探测不是一次 PING

Sentinel 的事件循环会周期性发送 PING、INFO 和 hello 消息。down-after-milliseconds 表示在整个窗口内没有收到可接受响应,Sentinel 才把实例标为主观下线;它不是 TCP connect timeout,也不是“连续失败次数”。

有效响应包括 PONGLOADINGMASTERDOWN;其他错误或无响应都不能证明实例可用。除此之外,如果逻辑 master 在 INFO 中宣称自己是 replica,Sentinel 也会把它视作异常。

Redis 事件循环被超长 Lua 脚本、大 Key 删除、内存换页或宿主机 CPU throttling 阻塞时,即使进程没有退出,也可能无法及时回答 PING。这是 Sentinel 把“进程活着但服务不可用”判为故障的必要设计,却也意味着阈值必须覆盖正常抖动。


4. SDOWN、ODOWN 与两个完全不同的门槛

4.1 SDOWN:单个 Sentinel 的本地判断

当 Sentinel S1 在 down-after-milliseconds 窗口内收不到 master 的有效回复,S1 将其标记为 SDOWN(Subjectively Down,主观下线)。

SDOWN 是本地意见:S1 认为 master 不可用,不代表其他 Sentinel 同意。

4.2 ODOWN:达到 quorum 的故障共识

S1 随后通过 SENTINEL is-master-down-by-addr 询问其他 Sentinel。若在有效时间窗口内,至少 quorum 个 Sentinel 都认为该 master 下线,master 才进入 ODOWN(Objectively Down,客观下线)。

ODOWN 只用于 master。Replica 或 Sentinel 自身只需要 SDOWN,因为它们下线不会直接触发 master failover。

4.3 quorum 不等于多数派

这是 Sentinel 配置中最常见的误解:

  • quorum 决定“多少 Sentinel 同意故障,才能标记 ODOWN”;

  • 多数派授权决定“某个 Sentinel 是否有权真正执行 failover”。

若有 5 个 Sentinel、quorum=2:两个 Sentinel 同意即可 ODOWN,但至少 3 个 Sentinel 可通信并投票,切换才会执行。执行授权的有效门槛可理解为:

max(配置的 quorum, floor(已知 Sentinel 总数 / 2) + 1)

Sentinel 总数建议 quorum可容忍 Sentinel 故障数说明
2不推荐0任一节点故障后无多数派
321常见最小生产配置
532更高控制面容错,运维成本也更高

奇数并不是算法的硬性语法要求,但能避免偶数部署付出更多节点却没有增加多数派容错能力。比“奇数”更重要的是独立故障域:3 个 Sentinel 放在同一宿主机或同一可用区,逻辑上仍接近单点。

4.4 状态变化全景


5. 第一次选举:谁来执行故障转移

多个 Sentinel 可能几乎同时观察到 ODOWN,但只能有一个执行者,否则它们可能提升不同的 replica。Sentinel 使用配置纪元 configuration epoch 和投票来解决这个问题。

5.1 Leader 选举过程

    1. 候选 Sentinel 增加当前 epoch;
    1. 通过 SENTINEL is-master-down-by-addr 请求其他 Sentinel 在该 epoch 投票;
    1. 每个 Sentinel 在一个 epoch 内只能投一票,先收到的合法请求通常先得票;
    1. 候选者获得所需授权后成为本轮 Leader;
    1. 若无人获胜,后续在新的 epoch 中重试。

它借鉴了 Raft 的 term/单轮单票思想,但 Sentinel 不是一个通用 Raft 日志复制系统,不应简单写成“Sentinel 使用 Raft”。更准确的表述是 Raft-like Leader election

5.2 epoch 为什么重要

epoch 是故障转移配置的逻辑版本号。网络分区恢复后,Sentinel 会倾向接受更新 epoch 的拓扑,使旧 master 最终被重新配置为新 master 的 replica。

Sentinel 的安全目标不是永远不出现两个能接收写入的进程——异步复制和网络分区下仍可能短暂脑裂——而是让控制面在每个配置纪元只授权一个故障转移,并让新配置最终收敛。

5.3 为什么 3 个 Sentinel 仍可能切不了

“有 3 个进程”不等于“有 3 张有效票”。以下任一问题都可能阻止授权:

  • Sentinel 之间的 26379 端口被 NetworkPolicy 或安全组阻断;

  • 两个 Sentinel 实际落在同一故障节点;

  • announce-ip / hostname 错误,其他 Sentinel 无法回连;

  • Sentinel 的持久化配置不可写,重启后丢失发现状态;

  • 配置的 master name 不一致,节点监控的并不是同一 master group;

  • 旧 Sentinel 长期未清理,被计入“已知 Sentinel 总数”,抬高多数派门槛。

应持续执行并监控:

redis-cli -h sentinel-0 -p 26379 SENTINEL CKQUORUM orders-cache redis-cli -h sentinel-0 -p 26379 SENTINEL MASTER orders-cache redis-cli -h sentinel-0 -p 26379 SENTINEL SENTINELS orders-cache

CKQUORUM 会同时检查 ODOWN 所需 quorum 和执行 failover 所需多数派,比只看进程存活更有价值。


6. 第二次选择:哪一个 Replica 应晋升

Leader 产生后,还要从 replica 集合中挑选新 master。算法不是“随机选一个活着的从库”,而是先过滤,再稳定排序。

6.1 第一阶段:淘汰不可靠 Replica

以下节点不会成为候选者:

  • 已处于 SDOWN;

  • 连接断开、无法获得有效 INFO

  • replica-priority=0

  • 与旧 master 断开时间过长。

官方给出的关键过滤条件是,replica 与 master 的断连时间若超过:

(down-after-milliseconds × 10) + master 已处于 SDOWN 的时间

则认为数据过旧,不适合晋升。这也是为什么只看 Pod Ready 没有意义:进程可用不代表复制数据足够新。

6.2 第二阶段:按三个键排序

通过过滤的 replica 按以下顺序选择:

    1. replica-priority 越小越优先
    1. 优先级相同时,replication offset 越大越优先
    1. offset 也相同时,run ID 字典序越小越优先,仅用于确定性决胜。
Replicapriorityoffsetrun ID结果
A509,991,000b91...胜出:优先级最低
B1009,999,900a12...offset 更新,但优先级劣后
C010,000,000c33...永不晋升

这个例子说明:priority 的权重高于数据新鲜度。不要为了“主 AZ 优先”设置悬殊 priority,却忽略它可能让 offset 更旧的 replica 晋升。

6.3 跨机房 priority 的取舍

同城双 AZ 场景常把本 AZ replica 设为 50,远端设为 100,以降低正常切换后的访问 RTT。但这是一种可用性/延迟偏好,不是数据一致性保证。

若业务更关心少丢数据,应该:

  • 尽量让候选 replica 使用相同 priority,让 offset 决胜;

  • 监控 master_repl_offset - slave_repl_offset

  • 使用 min-replicas-to-write 限制孤立 master 写入;

  • 对极少数关键写按需使用 WAIT / WAITAOF,同时承认它们仍不把 Redis 变成强一致数据库。


7. 故障转移状态机:切主不只是 REPLICAOF NO ONE

Leader 的完整动作可以概括为六个阶段:

Sentinel-aware Client其他 Replica候选 Replica其他 SentinelsLeader Sentinel旧 MasterSentinel-aware Client其他 Replica候选 Replica其他 SentinelsLeader Sentinel旧 Master旧 Master 恢复后会被纠正为新 Master 的 ReplicaPING 超时,标记 SDOWNis-master-down-by-addr同意下线与 epoch 投票达到 quorum,ODOWN;获得多数票REPLICAOF NO ONEINFO 显示 role=masterREPLICAOF new-master传播更高 configuration epoch查询时返回新 Master 地址新建连接并恢复命令

对应的内部语义是:

    1. WAIT_START:等待 Leader 授权;
    1. SELECT_SLAVE:筛选并选择 replica;
    1. SEND_SLAVEOF_NOONE:发送 REPLICAOF NO ONE
    1. WAIT_PROMOTION:通过 INFO 确认其角色已变为 master;
    1. RECONF_SLAVES:按 parallel-syncs 并发度重配其余 replica;
    1. UPDATE_CONFIG:发布新配置并结束 failover。

Sentinel 在确认新节点已晋升后即可把 failover 视为成功;其他 replica 的重新同步可能仍在继续。因此“主写已恢复”和“整个副本拓扑完全健康”是两个不同的观测点。

7.1 failover-timeout 的真实作用

failover-timeout 不是单一的“整个切换必须在 N 毫秒内完成”。它参与多处超时和重试节奏,包括限制同一 master 再次尝试 failover、等待晋升以及 replica 重配置。把它机械地从默认值压到 10 秒,可能在慢网络或大实例上制造反复切换。

正确做法是先测量:

  • 正常网络 P99/P99.9 RTT;

  • Redis 最长可接受事件循环延迟;

  • replica 晋升观察时间;

  • 全量/增量同步耗时;

  • 客户端重新发现与连接建立时间。

然后再定义阈值和告警,而不是照抄“3 秒检测、15 秒切换”。


8. 数据一致性:切换成功不等于零丢失

8.1 异步复制的窗口

Redis master 接受写入后通常立即响应客户端,再异步把命令传播给 replica。若 master 在命令尚未到达 replica 时故障,新主可能缺少这部分数据。Redis replication 文档 明确指出异步复制始终存在数据丢失窗口。

因此,商品案例把 PostgreSQL 作为真相源,Redis 只保存可重建数据。订单、支付、最终库存扣减不能只存在 Redis。

8.2 用 min-replicas-* 限制脑裂写入

# 至少有 1 个延迟不超过 5 秒的 replica,master 才接受写入 min-replicas-to-write 1 min-replicas-max-lag 5

当旧 master 落入少数网络分区,无法与 replica 正常复制时,它会停止接受写入,从而限制脑裂期间的错误写窗口。但这只是 best effort:延迟判断有采样粒度,不能提供线性一致性,也会在唯一 replica 维护时主动牺牲写可用性。

8.3 WAITWAITAOF 应按风险使用

WAIT 1 100 可让当前连接等待此前写命令被至少一个 replica 确认;较新 Redis 还提供 WAITAOF 等待 AOF fsync。它们能降低数据丢失概率,但仍不使 Redis 成为强一致存储。

不要给所有缓存 SET 增加同步等待。更合理的策略是:

  • 普通可重建缓存:异步复制,优先吞吐;

  • 高价值但仍可补偿的短状态:按命令使用 WAIT,并记录失败补偿;

  • 订单、资金、不可重建事实:先写事务数据库/持久事件,再异步更新或删除缓存。

8.4 超时后的“未知结果”

客户端写超时不代表 Redis 没执行。若服务端已执行 SET,但响应在网络中丢失,盲目重试非幂等命令会重复扣减。

生产规则是:

  • GET、MGET 等只读命令可做有界重试;

  • SET 覆盖写只有在业务语义幂等时才能重试;

  • INCR、列表 push、Lua 扣减等不得无条件自动重试;

  • 关键写必须携带业务幂等键,并在同一 Lua 脚本或真相数据库中原子判重。


9. 生产架构:把故障限制在缓存域内

9.1 推荐拓扑

设计要点:

  • 应用通过多个 Sentinel 地址和固定 master name 发现写主,不直连 Redis Pod IP;

  • Redis 和 Sentinel 分离部署,并使用 topology spread / anti-affinity 分散故障域;

  • L1 只保存 1~5 秒可容忍旧值,既削热 Key,也为切换提供短暂缓冲;

  • 数据库回源使用并发舱壁,宁可部分降级,也不能把数据库打死;

  • 商品缓存默认从 master 读取,避免 failover 后 replica 读产生额外陈旧窗口;

  • Sentinel 是 HA,不是扩容。单主写瓶颈出现时转 Redis Cluster,而不是增加 Sentinel。

9.2 为什么不推荐“Controller 修改 Redis Service Endpoints”作为默认方案

有些方案监听 +switch-master,再修改 Kubernetes Endpoints,让应用始终连接一个固定 Service。这会创造第二个控制面:Sentinel 认为 A 是 master,而自定义 Controller 或 EndpointSlice controller 可能暂时仍把流量发给 B。

它还需要处理事件丢失、Controller 重启、乐观锁冲突、EndpointSlice 所有权、readiness 与角色切换的竞态。除非遗留客户端完全不支持 Sentinel,并且团队愿意为这个控制器承担严格的对账与测试,否则优先使用成熟 Sentinel-aware 客户端、经过验证的 Operator 或托管 Redis。

9.3 Sentinel、Cluster、Proxy 和托管服务怎么选

方案适用条件优势主要代价
Sentinel单主容量足够,需自动切换简单成熟;保留非分片语义写入不能水平扩展;客户端必须感知
Redis Cluster数据或写吞吐超过单主原生分片与分片级 HA多 Key/事务受 hash slot 约束;迁移复杂
Proxy + Sentinel大量遗留客户端、需连接收敛对应用屏蔽拓扑多一跳、代理 HA、命令兼容性成本
Operator自建 K8s 且有平台团队声明式编排、自动修复质量取决于 Operator;升级需验证
云托管 Redis团队希望购买 SLA 和运维能力备份、监控、补丁更完整成本、厂商约束、网络与功能差异

10. Redis 与 Sentinel 的生产配置

下面是配置基线,不是可脱离环境照抄的“万能参数”。密码和证书必须来自 Secret 或外部密钥系统,不能写入 ConfigMap 和 Git。

10.1 Redis 数据节点

# redis.conf port 6379 protected-mode yes # 持久化按恢复目标选择;缓存也建议保留可接受的重启恢复能力 appendonly yes appendfsync everysec aof-use-rdb-preamble yes # 限制孤立主继续接受写入的窗口 min-replicas-to-write 1 min-replicas-max-lag 5 # 依据“峰值复制字节速率 × 可容忍断链时间 × 安全系数”估算 # 例如不是按实例内存固定百分比拍脑袋配置 repl-backlog-size 256mb repl-backlog-ttl 3600 # 每个节点都要配置,因为今天的 master 明天可能成为 replica replica-priority 100 replica-read-only yes # 防止慢 replica 的输出缓冲无限增长;阈值需结合带宽与全量同步测试 client-output-buffer-limit replica 512mb 128mb 60 # 避免危险命令造成全实例阻塞;具体值由平台 ACL 与运维流程决定 lazyfree-lazy-eviction yes lazyfree-lazy-expire yes

repl-backlog-size 的工程估算方式:

backlog >= 峰值复制字节率 × 预期最大断链秒数 × 1.5~2 安全系数

例如峰值复制流量 20 MiB/s,希望 10 秒闪断后仍能部分同步,至少需要约 300~400 MiB。最终要通过 INFO replication、网络故障演练和内存预算校准。

10.2 Sentinel

# sentinel.conf 必须可写,Sentinel 会持久化发现结果和新拓扑 port 26379 protected-mode yes sentinel monitor orders-cache redis-0.redis-headless.cache.svc.cluster.local 6379 2 sentinel down-after-milliseconds orders-cache 5000 sentinel failover-timeout orders-cache 60000 sentinel parallel-syncs orders-cache 1 # Redis 数据节点启用 ACL 时,给 Sentinel 单独的最小权限账号 sentinel auth-user orders-cache sentinel-replication sentinel auth-pass orders-cache ${SENTINEL_REDIS_PASSWORD} # Redis 6.2.4+ 可解析配置中的 hostname;上线前核对实际 Redis 版本 sentinel resolve-hostnames yes # 若客户端和节点证书依赖 DNS SAN,可让 Sentinel 对外通告 hostname sentinel announce-hostnames yes

关键解释:

  • down-after-milliseconds=5000 是案例起点,必须大于正常 P99.9 延迟、最长 GC/调度抖动和可接受事件循环阻塞;

  • parallel-syncs=1 让 replica 逐个指向新主,降低同时载入数据造成的读容量骤降;

  • Sentinel 会自动重写配置,因此只读 ConfigMap 不能直接作为运行时 sentinel.conf;可由 initContainer 复制模板到可写卷,再从该文件启动;

  • ${...} 是否会被容器自动替换取决于启动脚本。Redis 配置本身不会通用地完成环境变量插值,实际部署应通过 Secret 挂载生成最终配置或使用启动参数,避免误以为占位符已生效;

  • 若 Sentinel 自身启用 ACL/TLS,客户端也必须配置 Sentinel 凭据/信任链;数据节点凭据与 Sentinel 控制端凭据是两套身份。

10.3 参数取舍表

参数过小的风险过大的风险调优依据
down-after-milliseconds网络抖动误切主故障发现慢RTT、事件循环延迟、SLO
failover-timeout反复失败、拓扑振荡失败重试慢晋升与同步演练
parallel-syncs值大时复制/加载并发冲击值小时副本恢复慢网络、磁盘、可用读副本数
repl-backlog-size闪断后频繁全量同步占用 master 内存写字节率 × 断链预算
min-replicas-to-write值大时维护期拒写值小时数据保护弱可用性与 RPO 取舍

11. Kubernetes 落地:稳定身份不等于固定角色

11.1 StatefulSet 的正确理解

StatefulSet 提供稳定 Pod 名称和持久卷身份,但 redis-0 不会永远是 master。Sentinel 切换后,redis-1 可能成为 master,旧主恢复后成为 replica。任何把 Pod 序号等同角色的脚本都存在隐患。

Headless Service 提供每个 Pod 的稳定 DNS:

apiVersion: v1 kind: Service metadata: name: redis-headless namespace: cache spec: clusterIP: None publishNotReadyAddresses: true selector: app: redis ports: - name: redis port: 6379

publishNotReadyAddresses 是否开启要结合启动发现流程评估:它能帮助节点在未 Ready 时互相发现,但也意味着 DNS 可解析不等于服务已就绪。应用不能把这个 headless service 当成 master Service 使用。

11.2 Sentinel 的调度和可用性约束

apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: redis-sentinel namespace: cache spec: minAvailable: 2 selector: matchLabels: app: redis-sentinel --- apiVersion: apps/v1 kind: Deployment metadata: name: redis-sentinel namespace: cache spec: replicas: 3 selector: matchLabels: app: redis-sentinel template: metadata: labels: app: redis-sentinel spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: redis-sentinel containers: - name: sentinel image: redis:<组织验证并锁定的版本> args: ["redis-server", "/run/redis/sentinel.conf", "--sentinel"] ports: - name: sentinel containerPort: 26379

这里故意没有伪造完整镜像版本和 Secret 名称。生产清单还必须补齐:固定镜像 digest、资源 request/limit、startup/readiness probe、只读根文件系统、非 root 用户、NetworkPolicy、TLS、可写配置卷和 initContainer。它们依赖组织镜像布局与证书体系,不能从文章中替你决定。

11.3 云原生部署的五个硬检查

    1. 可达地址:Sentinel 发现的 replica 地址能否从所有 Sentinel 和应用网络访问;
    1. 配置可写:运行时 sentinel.conf 是否可原子重写且重启后保留;
    1. 故障域分散:Redis replica 与 Sentinel 是否跨 node/AZ;
    1. PDB 不滥用:PDB 只约束自愿驱逐,不防节点宕机;不能把维护永久卡死;
    1. 角色探针正确:readiness 只判断“可服务”,还是要求当前 role=master,取决于该 Service 的用途。

如果没有平台团队持续验证自建拓扑,优先选择成熟 Operator 或托管服务,而不是维护一套依赖脆弱 shell 脚本的主从编排。


12. Spring Boot 生产级接入

以下示例基线为 Java 21、Spring Boot 3.x、Lettuce。依赖版本应由组织 BOM 锁定,并用集成测试验证;官方属性可参考 Spring Boot Redis 文档。代码的核心目标是:

  • 由 Sentinel 发现当前 master;

  • Redis 故障时用短时 L1 缓存降级;

  • 限制数据库回源并发;

  • 只对可安全重试的读取做恢复;

  • 暴露命中、降级和拒绝指标。

12.1 目录结构

src/main/java/com/acme/catalog/ ├── CatalogApplication.java ├── api/ProductController.java ├── application/ProductQueryService.java ├── domain/ProductView.java ├── infrastructure/ProductRepository.java └── infrastructure/RedisClientConfig.java src/main/resources/ └── application.yml

12.2 Maven 依赖

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

不要同时引入 Jedis 和 Lettuce,也不要为了“连接池看起来专业”默认给 Lettuce 套池。Lettuce 的普通非阻塞连接线程安全且可复用;只有阻塞命令、事务等独占连接场景才应单独评估池化。

12.3 配置文件

spring: application: name: catalog-service data: redis: database: 0 username: ${REDIS_DATA_USERNAME:catalog-app} password: ${REDIS_DATA_PASSWORD} connect-timeout: 500ms timeout: 800ms sentinel: master: orders-cache nodes: - sentinel-0.redis-sentinel.cache.svc.cluster.local:26379 - sentinel-1.redis-sentinel.cache.svc.cluster.local:26379 - sentinel-2.redis-sentinel.cache.svc.cluster.local:26379 username: ${REDIS_SENTINEL_USERNAME:sentinel-client} password: ${REDIS_SENTINEL_PASSWORD} lettuce: shutdown-timeout: 200ms management: endpoints: web: exposure: include: health,prometheus health: redis: enabled: true catalog: cache: redis-ttl: 10m local-ttl: 3s local-maximum-size: 10000 db-bulkhead-permits: 32

注意区分数据节点认证和 Sentinel 自身认证。不同 Spring Boot 小版本的属性模型可能变化,升级时要以所用版本的配置元数据和启动日志为准,不能只看 IDE 没报红。

12.4 连接配置:一致性优先时只读 master

package com.acme.catalog.infrastructure; import io.lettuce.core.ReadFrom; import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedisClientConfig { @Bean LettuceClientConfigurationBuilderCustomizer masterReadOnly() { return builder -> builder.readFrom(ReadFrom.MASTER); } }

原方案常见的 REPLICA_PREFERRED 会把读流量发送到异步 replica,切换或网络抖动时可能读到旧值。只有业务明确接受最终一致,并且已测试读路由与故障行为时,才考虑 replica 读。商品详情案例优先 master 读,通过 L1 缓存扩展读容量。

12.5 领域对象和仓储边界

package com.acme.catalog.domain; import java.math.BigDecimal; public record ProductView( long productId, String title, BigDecimal displayPrice, boolean saleable, long version) { }

package com.acme.catalog.infrastructure; import com.acme.catalog.domain.ProductView; import java.util.Optional; public interface ProductRepository { Optional<ProductView> findPublishedById(long productId); }

实际项目可用 Spring Data JDBC/JPA 实现该接口。Redis 只保存 ProductView,不让领域层依赖 Redis API;这样降级和迁移到 Cluster/托管服务时,业务模型不受影响。

12.6 查询服务:L1 合并请求、Redis L2、受控回源

package com.acme.catalog.application; import com.acme.catalog.domain.ProductView; import com.acme.catalog.infrastructure.ProductRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import java.time.Duration; import java.util.NoSuchElementException; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadLocalRandom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; @Service public class ProductQueryService { private static final Logger log = LoggerFactory.getLogger(ProductQueryService.class); private static final String KEY_PREFIX = "catalog:product:"; private final StringRedisTemplate redis; private final ProductRepository repository; private final ObjectMapper objectMapper; private final Cache<Long, ProductView> localCache; private final Semaphore dbBulkhead; private final Duration redisTtl; private final Counter redisFailure; private final Counter dbRejected; public ProductQueryService( StringRedisTemplate redis, ProductRepository repository, ObjectMapper objectMapper, MeterRegistry meterRegistry, @Value("${catalog.cache.redis-ttl:10m}") Duration redisTtl, @Value("${catalog.cache.local-ttl:3s}") Duration localTtl, @Value("${catalog.cache.local-maximum-size:10000}") long localMaximumSize, @Value("${catalog.cache.db-bulkhead-permits:32}") int dbPermits) { if (dbPermits < 1 || localMaximumSize < 1) { throw new IllegalArgumentException("cache capacity and db permits must be positive"); } this.redis = redis; this.repository = repository; this.objectMapper = objectMapper; this.redisTtl = redisTtl; this.localCache = Caffeine.newBuilder() .maximumSize(localMaximumSize) .expireAfterWrite(localTtl) .recordStats() .build(); this.dbBulkhead = new Semaphore(dbPermits); this.redisFailure = meterRegistry.counter("catalog.cache.redis.failure"); this.dbRejected = meterRegistry.counter("catalog.cache.db.rejected"); } public ProductView get(long productId) { if (productId <= 0) { throw new IllegalArgumentException("productId must be positive"); } // Caffeine Cache#get 会在单 Pod 内合并同一个 Key 的并发加载。 return localCache.get(productId, this::loadFromRedisOrDatabase); } public void evict(long productId) { localCache.invalidate(productId); try { // UNLINK 将实际内存回收异步化,适合 value 可能较大的缓存对象。 redis.unlink(KEY_PREFIX + productId); } catch (DataAccessException ex) { redisFailure.increment(); log.warn("redis eviction failed, productId={}", productId, ex); // Redis TTL 是删除失败的最终兜底;强一致场景应使用事务 Outbox 重试删除。 } } private ProductView loadFromRedisOrDatabase(long productId) { String key = KEY_PREFIX + productId; try { String json = redis.opsForValue().get(key); if (json != null) { return objectMapper.readValue(json, ProductView.class); } } catch (DataAccessException | JsonProcessingException ex) { redisFailure.increment(); log.warn("redis read degraded, productId={}", productId, ex); } if (!dbBulkhead.tryAcquire()) { dbRejected.increment(); throw new CacheOriginOverloadedException("database fallback is saturated"); } try { ProductView product = repository.findPublishedById(productId) .orElseThrow(() -> new NoSuchElementException("product not found: " + productId)); writeRedisBestEffort(key, product); return product; } finally { dbBulkhead.release(); } } private void writeRedisBestEffort(String key, ProductView product) { try { long jitterSeconds = ThreadLocalRandom.current().nextLong(0, 61); redis.opsForValue().set( key, objectMapper.writeValueAsString(product), redisTtl.plusSeconds(jitterSeconds)); } catch (DataAccessException | JsonProcessingException ex) { redisFailure.increment(); log.warn("redis refill failed, productId={}", product.productId(), ex); } } public static final class CacheOriginOverloadedException extends RuntimeException { public CacheOriginOverloadedException(String message) { super(message); } } }

这段代码落实了四个高并发策略:

  • 每个 Pod 内,同一 Key 的冷启动由 Caffeine 合并为一次加载;

  • Redis 切换的短窗口由 3 秒 L1 缓冲;

  • miss 回源最多 32 个并发,保护数据库连接池;

  • TTL 加随机抖动,降低同一批 Key 集中过期。

它没有使用 Redis 分布式锁阻止全局回源,因为 failover 时锁本身也不可用或可能丢失。对于 60 个 Pod,最坏同一冷 Key 仍可能有约 60 次回源;若数据库不能承受,应增加请求合并层、热点预热或专用查询缓存,而不是把可靠性建立在同一个故障中的 Redis 锁上。

12.7 API 与异常映射

package com.acme.catalog.api; import com.acme.catalog.application.ProductQueryService; import com.acme.catalog.application.ProductQueryService.CacheOriginOverloadedException; import com.acme.catalog.domain.ProductView; import jakarta.validation.constraints.Positive; import java.util.NoSuchElementException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Validated @RestController @RequestMapping("/api/products") public class ProductController { private final ProductQueryService queryService; public ProductController(ProductQueryService queryService) { this.queryService = queryService; } @GetMapping("/{productId}") public ProductView get(@PathVariable @Positive long productId) { return queryService.get(productId); } @ExceptionHandler(NoSuchElementException.class) ResponseEntity<ErrorResponse> notFound(NoSuchElementException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("PRODUCT_NOT_FOUND", ex.getMessage())); } @ExceptionHandler(CacheOriginOverloadedException.class) ResponseEntity<ErrorResponse> overloaded(CacheOriginOverloadedException ex) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .header("Retry-After", "1") .body(new ErrorResponse("CACHE_ORIGIN_OVERLOADED", ex.getMessage())); } record ErrorResponse(String code, String message) {} }

503 + Retry-After 比让请求在线程池里无限排队更可控。网关只能对 GET 做小次数、带抖动的重试,且总重试时长必须小于调用方预算;服务端不要再叠加多层重试,避免乘法放大。

12.8 写后失效与一致性

商品更新应先提交数据库事务,再删除缓存:

更新数据库成功 → 事务提交 → 删除 L1/L2 → 下次读取回填

如果删除失败,短 TTL 提供最终兜底;若业务不能接受 10 分钟旧值,应使用事务 Outbox:数据库事务内同时写商品变更和 outbox,后台消费者幂等执行缓存删除,成功后标记事件完成。不要使用“数据库 + Redis 双写事务”的假原子方案。

更新并发时可把 ProductView.version 带入 value,并在回填 Lua 中拒绝旧版本覆盖新版本;这比依赖请求到达顺序更可靠。


13. 高并发治理:切换时最危险的是放大效应

13.1 连接风暴

60 个应用 Pod 如果每个维护 100 个连接,切换时最多可能同时创建数千条 TCP/TLS 连接。处理策略:

  • 使用客户端默认共享连接,不盲目增大池;

  • 连接建立和命令分别设置超时;

  • 重连采用指数退避与随机抖动;

  • 设置连接总量、文件描述符和 Redis maxclients 预算;

  • 发布时滚动启动,避免应用和 Redis 同时全量重启;

  • TLS 场景单独压测握手 CPU。

13.2 缓存击穿与雪崩

风险典型症状处理方式
热 Key单 Key 占据大量带宽/CPUL1 缓存、拆 value、热点预热、业务分片
缓存击穿一个热 Key 失效后并发回源单机请求合并、全局回源限额
缓存雪崩大批 Key 同时过期或 Redis 切换TTL 抖动、L1、降级、限流
缓存穿透不存在 ID 持续查询参数校验、短时 negative cache、布隆过滤器
大 Key删除/序列化/复制阻塞拆 Key、UNLINK、持续扫描 bigkeys/memory usage

13.3 资源隔离

  • Redis I/O 不与数据库回源共用自定义线程池;

  • 数据库连接池大小应与回源 Semaphore 对齐,而不是让 500 个请求争 30 个连接;

  • 管理/探针流量与业务流量设置独立超时;

  • Sentinel 端口只向应用和 Redis 管理网络开放;

  • 大促前冻结高风险全量扫描、AOF rewrite 和宿主机迁移,或至少把它们纳入容量预算。

13.4 Sentinel 的扩展性边界

增加 replica 只能扩读、增强 HA;增加 Sentinel 只增强控制面容错;两者都不能扩 master 写吞吐。出现以下信号时应评估 Redis Cluster:

  • 单实例内存接近安全上限,fork/COW 成本不可接受;

  • master CPU 或网络带宽持续接近预算;

  • 写吞吐需要水平扩展;

  • 单个复制/恢复单元过大,RTO 无法满足。

迁移到 Cluster 前必须盘点 Lua、事务、multi-key 命令和 key tag,不能只替换连接地址。


14. 可观测性:要同时看控制面、数据面和客户端

14.1 Sentinel 指标与事件

至少监控:

  • SENTINEL CKQUORUM orders-cache 是否通过;

  • 已知 Sentinel 数、可用 replica 数;

  • s_downo_down 标志;

  • +sdown+odown+try-failover+elected-leader+selected-slave+promoted-slave+switch-master-odown 事件;

  • failover 持续时间和单位时间 failover 次数。

__sentinel__:hello 是 Sentinel 自动发现频道,不是应用监听主切换的频道。运维事件可以 PSUBSCRIBE * 或订阅 +switch-master,但 Pub/Sub 不持久化;监控系统必须同时轮询当前状态,不能把事件订阅当唯一事实源。

14.2 Redis 数据面

  • connected_slavesmaster_repl_offset、每个 replica offset/lag;

  • master_sync_in_progresssync_partial_ok/errsync_full

  • instantaneous_ops_per_sec、网络输入输出、连接数;

  • used_memorymem_fragmentation_ratio、evicted keys;

  • latest_fork_usec、AOF rewrite/RDB 状态;

  • command latency、slowlog、event-loop latency。

不要仅用 CPU 作为容量指标。Redis 常先受单核、网络带宽、大 Key、fork 或客户端输出缓冲影响。

14.3 客户端与业务指标

  • Redis 命令成功率、超时率和 P50/P95/P99;

  • 当前连接的 master 地址变化;

  • 重连次数和连接建立耗时;

  • L1/L2 命中率、数据库回源量、回源拒绝数;

  • 503 降级率、接口整体延迟和错误预算消耗。

建议告警关联而不是孤立触发:例如 +odown 同时伴随 Redis timeout、DB 回源上升,才是影响业务的高优先级事件。

14.4 日志安全

日志可记录 master name、节点地址、epoch、切换耗时和业务 trace ID,但不得打印 Redis 密码、ACL token、完整缓存 value 或用户敏感数据。Sentinel 与 Redis 审计账号分离,轮换 Secret 时要演练 Sentinel、replica 和客户端三条认证链。


15. 故障演练:验证的是完整链路,不是“Pod 能重启”

15.1 上线前检查

# 1. 从每个 Sentinel 检查 quorum 和多数派 redis-cli -h sentinel-0 -p 26379 SENTINEL CKQUORUM orders-cache # 2. 确认当前主地址 redis-cli -h sentinel-0 -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME orders-cache # 3. 确认候选 replica、priority、flags、offset redis-cli -h sentinel-0 -p 26379 SENTINEL REPLICAS orders-cache # 4. 从数据节点确认复制健康 redis-cli -h current-master -p 6379 INFO replication # 5. 确认 Sentinel 配置文件可写且重启后保留新 master redis-cli -h sentinel-0 -p 26379 SENTINEL FLUSHCONFIG

认证和 TLS 参数应从安全注入,不应出现在 shell history。

15.2 五类演练矩阵

演练注入方式必须验证
master 进程退出优雅停止 Redis检测、选举、晋升、客户端恢复
master 节点宕机排空/关停工作节点Redis 与 Sentinel 是否同故障域
网络分区NetworkPolicy/故障注入工具少数派不切主,旧主是否限写
replica 落后限带宽或暂停 replica选主是否避开旧副本
Sentinel 丢失停 1 个、再停第 2 个CKQUORUM、告警和不可切换边界

不要在生产直接用 SENTINEL FAILOVER 冒充所有故障测试:手工 failover 跳过了真实的故障判定和部分授权路径。它适合验证计划切换,不足以验证网络分区。

15.3 验收指标

一次演练至少记录:

T0 故障注入 T1 首个 Sentinel 标记 SDOWN T2 ODOWN T3 Leader elected T4 replica promoted T5 client first successful command to new master T6 all replicas healthy

T5 - T0 衡量业务恢复,以 T6 - T0 衡量拓扑恢复;同时统计错误请求数、DB 峰值回源、是否出现未知结果写入和是否消耗错误预算。

15.4 自动化集成测试思路

在测试环境启动 1 master + 2 replicas + 3 Sentinels:

  1. 写入带版本号的商品缓存;
  2. 查询 Sentinel 记录旧 master 地址;
  3. 停止旧 master 或隔离网络;
  4. 轮询多个 Sentinel,直到它们报告同一个新 master;
  5. 用应用客户端执行 GET/幂等 SET;
  6. 恢复旧 master,断言其变为 replica;
  7. 检查整个过程中数据库回源并发没有超过配置值。

测试必须有总超时,失败时保存 Sentinel 日志、INFO replication 和应用连接日志,避免只得到一个模糊的“等待超时”。


16. 常见错误方案与修正

错误一:两个 Sentinel 也算高可用

两个 Sentinel 任一故障后都无法形成多数派。修正为至少 3 个,并分散到独立故障域。

错误二:quorum=2 就表示两票可以切主

两票只可能完成 ODOWN;真正执行还需要多数派授权。使用 CKQUORUM 同时验证两个条件。

错误三:应用订阅 __sentinel__:hello 就能感知切换

该频道服务于 Sentinel 互相发现。应用应使用支持 Sentinel 的驱动,通过 master name 查询并重连;运维若监听事件,使用 +switch-master 等频道并配合状态轮询。

错误四:StatefulSet 的 redis-0 永远是 master

Pod 身份稳定,Redis 角色会变化。应用和探针都不能用序号推断角色。

错误五:切换时重试所有命令

非幂等写可能重复执行。只对安全读取做有界重试,写入使用业务幂等与补偿。

错误六:Replica 读取天然提升可用性

Replica 读会引入异步复制陈旧窗口,failover 期间更明显。先确认业务一致性需求,再选择 REPLICA_PREFERRED

错误七:Sentinel 能解决 Redis 容量问题

Sentinel 不分片。单主资源不足时,优化数据模型、拆实例或迁移 Cluster。

错误八:把 sentinel.conf 挂在只读 ConfigMap

Sentinel 必须重写配置保存拓扑和 epoch。应将模板复制到可写持久卷,或交给经过验证的 Operator 管理。

错误九:通知脚本直接修改 Nacos/Endpoints

脚本有超时、重试和并发限制,Pub/Sub 事件也不持久。把它变成关键流量控制面会扩大故障面;优先使用客户端原生发现。

错误十:min-replicas-to-write 可以彻底消除脑裂

它只限制风险窗口,并以写可用性为代价,不提供强一致。真相数据仍需可靠持久存储和补偿流程。


17. 从告警到恢复的操作手册

17.1 自动切换正常时

  1. 确认 +odown、Leader、晋升和 +switch-master 事件顺序;
  2. 从至少两个 Sentinel 查询新 master,避免读取单个过期视图;
  3. 验证新 master 的 role、复制 offset、连接数和延迟;
  4. 验证客户端已迁移,旧 master 不再有业务写连接;
  5. 等旧 master 恢复为 replica 后再解除事件状态;
  6. 对比数据库真相源,评估切换窗口是否有缓存写丢失。

17.2 ODOWN 但未切换时

按顺序检查:

  1. CKQUORUM 是否缺多数派;

  2. SENTINEL SENTINELS 是否包含陈旧或不可达节点;

  3. SENTINEL REPLICAS 是否存在健康、priority 非 0 的候选;

  4. Sentinel 到 replica 的认证、TLS、DNS、端口是否通;

  5. failover 是否处于等待/重试窗口;

  6. 日志是否出现 -failover-abort-no-good-slave 等终止事件。

在原因未知时不要连续执行手工 failover;先保留现场,再决定恢复多数派、修复候选 replica 或执行受控切换。

17.3 客户端没有恢复时

  1. 确认应用使用 master name,而非旧 IP;
  2. 从应用 Pod 访问所有 Sentinel,并查询新 master;
  3. 检查数据节点 ACL/TLS 是否允许新连接;
  4. 检查连接池是否耗尽、DNS 是否缓存、重连是否被熔断;
  5. 检查新 master maxclients、CPU 和握手负载;
  6. 必要时滚动恢复客户端,但不要同时重启全部 Pod。

18. 最终设计清单

上线 Sentinel 前,逐项回答“是”:

  • 至少 3 个 Sentinel,并跨独立 node/AZ;

  • quorum 和多数派均经 CKQUORUM 验证;

  • 至少两个健康 replica,复制延迟有监控;

  • 所有候选节点都正确设置 replica-priority

  • Sentinel 配置可写、可持久化、可在重启后恢复;

  • 应用使用 Sentinel-aware 客户端和统一 master name;

  • 数据节点与 Sentinel 的 ACL/TLS 身份分离;

  • 读写命令有明确超时,重试只用于安全操作;

  • L1 缓存、回源限额、TTL 抖动已经落实;

  • Redis 不是订单/支付等不可重建数据的唯一真相源;

  • min-replicas-* 的可用性代价已被业务接受;

  • K8s 反亲和、PDB、资源和 NetworkPolicy 已验证;

  • master 宕机、网络分区、replica 落后和 Sentinel 丢失均完成演练;

  • 指标覆盖 Sentinel、Redis、客户端和数据库回源四层;

  • 有明确的 Cluster/托管服务容量演进阈值。


19. 总结

理解 Redis Sentinel,要抓住五条主线:

  1. 故障判定有两层:SDOWN 是单节点意见,ODOWN 需要达到 quorum;

  2. ODOWN 不等于可以切主:执行 failover 还需要 Sentinel 多数派授权;

  3. 选主先过滤再排序:健康度 → priority → replication offset → run ID;

  4. 切主是状态机:晋升新主、重配副本、传播配置、客户端重连缺一不可;

  5. 高可用是端到端能力:Sentinel 只解决控制面,数据一致性、缓存降级、回源保护、幂等和演练仍由架构与工程体系负责。

Sentinel 的价值不在于把故障藏起来,而在于让故障有确定的判断者、唯一的执行者和可收敛的新拓扑。真正生产级的方案,还要确保切换期间数据库不会被击穿、客户端不会形成连接风暴、非幂等写不会被重复执行,并且团队能用演练数据说明系统在故障中究竟会发生什么。

当单主容量仍满足业务、数据可重建且团队能管理这些边界时,Sentinel 是简单而成熟的选择;当写吞吐、数据规模或跨地域一致性超出单主模型时,就应果断演进到 Redis Cluster、托管服务或更适合的数据系统,而不是继续给 Sentinel 堆职责。


参考资料

  • Redis Sentinel:High availability with Redis Sentinel

  • Redis Sentinel client specification

  • Redis replication

  • Redis WAITAOF command

  • Spring Boot:Working with NoSQL Technologies



本文的引用仅限自我学习如有侵权,请联系作者删除。
参考知识
Redis主从最全详解(原理+架构+流程)
Redis 高可用进阶(二):Sentinel 哨兵与选主算法


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值