Redis 哨兵模式中客户端访问服务器的过程详解

目录

一、哨兵模式中客户端如何访问服务器

[1. 详细步骤分解](#1. 详细步骤分解)

(1)第一阶段:故障发现与自动转移(由哨兵集群自动完成)

(2)第二阶段:客户端感知与服务发现

(3)第三阶段:恢复正常访问

[2. 总结与关键点](#2. 总结与关键点)

[二、"引导连接" 和 "常规操作"](#二、“引导连接” 和 “常规操作”)

[1. 核心原则](#1. 核心原则)

[2. 详细解释](#2. 详细解释)

(1)启动时:通过哨兵"问路"(必须通过哨兵)

(2)运行时:直接访问(直连主)

(3)故障时:再次"问路"(必须通过哨兵)

[3. 总结](#3. 总结)

三、服务发现与代理

[1. 核心区别对比](#1. 核心区别对比)

[2. 详细解释](#2. 详细解释)

[(1)Redis 哨兵模式:"问路式"](#(1)Redis 哨兵模式:"问路式")

[(2)MySQL Router 模式:"代理式"](#(2)MySQL Router 模式:"代理式")

(3)风格比较

(4)现代架构的演进趋势

[3. 总结与选择](#3. 总结与选择)


一、哨兵模式中客户端如何访问服务器

为了更直观地理解,我们首先通过一张时序图来俯瞰整个交互流程,然后再分步进行详细解释。

1. 详细步骤分解

整个过程可以分为三个阶段:故障发现与转移、客户端感知与切换、恢复正常访问。

(1)第一阶段:故障发现与自动转移(由哨兵集群自动完成)

这一步是哨兵模式的核心,在客户端无感知的情况下完成。

  • 主观下线

每个哨兵节点会定期(每秒一次)向所有主节点、从节点以及其他哨兵节点发送 PING 命令。如果一个主节点在配置的 down-after-milliseconds 时间内未有效回复 PING(可能是返回错误如 LOADING,也可能是超时无响应),那么发现它的哨兵就会将其标记为 "主观下线"。这意味着单个哨兵认为这个主节点可能挂了。

  • 客观下线

当一个哨兵将主节点标记为主观下线后,它会向其他哨兵节点发送 SENTINEL is-master-down-by-addr 命令,询问它们是否也认为该主节点下线了。如果超过法定数量(通常是哨兵总数的一半以上,比如 3 个哨兵需要 2 票)的哨兵都报告该主节点主观下线,那么发起询问的哨兵就会将这个主节点标记为 "客观下线"。这意味着从集群层面确认主节点确实故障了,可以开始执行故障转移。

  • 选举领头哨兵

主节点被确认为客观下线后,所有哨兵会通过一种 Raft 共识算法来选举一个 "领头哨兵" 。选举规则包括互相投票,直到某个哨兵获得多数票。领头哨兵负责完成后续的故障转移操作,这避免了多个哨兵同时执行转移导致混乱。

  • 故障转移

领头哨兵会从原主节点的从节点列表中,根据一定的规则(优先级 slave-priority、复制偏移量、Run ID 等)选出一个最优的从节点。领头哨兵向这个被选中的从节点发送 SLAVEOF NO ONE 命令,使其停止复制原主节点,并晋升为新的主节点。

领头哨兵然后向其他所有从节点发送 SLAVEOF 命令,让它们去复制这个新的主节点。

最后,领头哨兵会更新自己的配置,将旧主节点信息覆盖为新主节点信息,并通过发布/订阅功能通知整个哨兵集群。

(2)第二阶段:客户端感知与服务发现

当故障转移完成后,客户端需要知道新的主节点地址。

  • 首次连接与订阅

在客户端启动时,它配置的不是 Redis 主节点的地址,而是多个哨兵节点的地址列表。客户端会随机选择一个哨兵节点进行连接,并执行 SENTINEL get-master-addr-by-name <master-name> 命令,来获取当前有效的主节点地址和端口。这里的 <master-name> 是在哨兵配置中定义的逻辑主节点名称。为了能持续感知主节点的变化,客户端还会向哨兵订阅 +switch-master 频道的事件。

  • 感知变化与重连接

当发生故障转移后,领头哨兵会向 +switch-master 频道发布一条消息,内容包含了旧主节点和新主节点的地址信息。订阅了该频道的客户端会立即收到这条消息。客户端收到消息后,知道主节点已经变更,会主动关闭与旧主节点的连接,并使用新的主节点地址重新建立连接。

(3)第三阶段:恢复正常访问

  • 命令重定向

在客户端成功连接到新的主节点之后,所有后续的读写命令(默认情况下,写命令和读命令都会发往主节点)都会发送到这个新的主节点上。如果客户端在故障转移的瞬间发送了命令,这些命令可能会失败(连接错误或超时)。一个健壮的客户端库应该具备重试机制,在捕获到异常后,会回到第二阶段,重新向哨兵查询主节点地址,然后重试命令。

2. 总结与关键点

  • 解耦与高可用:哨兵模式将监控、通知、自动故障转移与数据存储分离开,实现了 Redis 的高可用。
  • 客户端角色:客户端不再是简单的直连,而是作为一个主动的服务发现参与者。它需要与哨兵交互并监听事件。
  • 不是强一致性:在故障转移的瞬间,可能会有少量数据丢失(原主节点宕机前未同步到新主的数据)。同时,客户端可能会遇到短暂的不可用或命令失败,需要重试。
  • 健壮的客户端库:整个流程的顺畅度极大地依赖于客户端库的实现。一个好的 Redis 客户端库(如 Lettuce、Jedis 等)已经内置了对哨兵模式的支持,封装了上述的服务发现、订阅和重连逻辑,对应用开发者基本透明。

通过以上全过程,Redis 哨兵模式确保了在主节点发生故障时,集群能够自动恢复服务,从而保障了系统的高可用性。

二、"引导连接" 和 "常规操作"

为了进一步清晰说明客户端不是每次连接都通过哨兵找到主,我们区分一下"引导连接" 和 "常规操作"。

1. 核心原则

  • 引导连接:当客户端第一次启动或完全丢失了主节点信息时,它必须通过查询哨兵来"引导"自己,找到当前的主节点地址。
  • 常规操作:在成功引导之后,在没有发生故障转移的正常运行期间,客户端会直连已知的主节点,以获得最佳性能和最低延迟。

让我们通过一个修正后的流程图来展示这个更精确的过程,特别注意其中"直连主"与"问询哨兵"的两种连接路径:

2. 详细解释

(1)启动时:通过哨兵"问路"(必须通过哨兵)

当你的应用程序刚启动时,客户端的连接池里是空的,它不知道主节点是谁。这时它的行为是:

  • 读取配置(通常是多个哨兵的地址列表)。
  • 从中选一个可用的哨兵进行连接。
  • 向该哨兵发送 SENTINEL get-master-addr-by-name <master-name> 命令。
  • 哨兵返回当前的主节点 IP 和端口。
  • 客户端拿到这个地址后,会缓存下来,并直接用这个地址与主节点建立连接。

所以,在第一次连接建立时,客户端是通过哨兵找到了主节点,然后直连主节点。

(2)运行时:直接访问(直连主)

在成功获取主节点地址并建立连接后,在没有发生故障切换的漫长稳定运行期内:

  • 客户端的所有 GET、SET 等数据操作命令,都是直接发送给它缓存的那个主节点地址的。
  • 它不会在每次执行命令前都去问一次哨兵。
  • 这样做的好处是性能极高,没有中间层,延迟最低。如果每次命令都通过哨兵转发,哨兵会成为性能和单点故障的瓶颈,这就失去了 Redis 高性能的优势。

(3)故障时:再次"问路"(必须通过哨兵)

当主节点宕机,哨兵完成故障转移后:

  • 哨兵会更新自己的主节点信息。
  • 同时,它会向它负责的 +switch-master 频道发布一个消息。
  • 客户端在启动时,除了查询主节点地址,还会订阅这个频道。
  • 当客户端收到 +switch-master 事件后,它就知道了两件事:旧的主节点已经失效;新的主节点是谁(消息体中包含新地址)。
  • 客户端会:关闭与旧主节点的连接池;用新的主节点地址重新建立直连;后续的所有命令又恢复为直连新的主节点。

如果客户端因为网络问题没有收到订阅消息,它会有降级策略:当发现与主节点的连接中断时,它会退回到初始的"引导连接"步骤,重新询问哨兵以获取新的主节点地址。

3. 总结

场景 访问模式 说明
应用启动/首次连接 通过哨兵查询,然后直连主 一次性的"服务发现"行为。
正常运行期间 直连主节点 常态,为了高性能。
故障转移之后 通过哨兵事件感知,然后直连新主 被动通知或主动重查,之后恢复直连。

在正常情况下,没有发生主从切换时,整个数据访问流程可以概括为:

  • 初始化引导(一次性动作):客户端通过查询哨兵集群,获取当前主节点的真实地址。这个过程只在应用启动或重建连接池时发生。
  • 长期稳定运行(常态):客户端将获取到的主节点地址缓存起来。此后,所有的 GET、SET、DEL 等数据操作命令,都直接发送给这个缓存的主节点地址。在此期间,哨兵集群完全处于后台静默状态,客户端与它们没有任何数据交互。数据通道是客户端与主节点之间的直连通道。

这样设计的核心目的就是为了极致的性能:

  • 低延迟:消除了每次命令的额外网络跳转(如果经过哨兵,会多一跳)。
  • 高吞吐:避免了哨兵成为数据平面的性能瓶颈。
  • 架构清晰:将管理控制平面(哨兵,负责监控和切换)和数据平面(Redis 主从节点,负责数据处理)分离开。

三、服务发现与代理

Redis 哨兵模式和 MySQL Router 代表了服务发现与代理的两种核心思想。为了更直观地展示这一关键区别,我们可以用下图来对比:

1. 核心区别对比

特性 Redis 哨兵模式 MySQL Router
架构模式 智能客户端 + 服务发现 代理层 + 透明转发
数据路径 客户端 -> 主节点 (直接) 客户端 -> Router -> 主节点 (间接)
客户端角色 主动,需要集成哨兵客户端逻辑 被动,只需将 Router 视为一个普通的 MySQL 服务器
性能 更低延迟,少一次网络跳转 略高延迟,多经过一次代理
复杂度 客户端逻辑复杂,需要处理连接切换 客户端逻辑简单,所有复杂性封装在 Router 中
耦合度 客户端与 Redis 服务发现机制耦合 客户端与 MySQL 集群架构解耦

2. 详细解释

(1)Redis 哨兵模式:"问路式"

比喻:就像您开车去一个目的地。您出发前先用导航(哨兵)查一下地址(主节点 IP),然后就直接开车到目的地了。除非道路施工或封路(主节点宕机),导航才会重新规划路线,您再开往新地址。

优点:

  • 性能最佳:没有中间商赚差价,网络延迟最低。
  • 架构简单:在稳定状态下,数据路径非常直接。

缺点:

  • 客户端沉重:客户端需要实现服务发现、订阅事件、故障重试等逻辑,变得"智能"且复杂。
  • 耦合性高:客户端必须知道哨兵的地址和协议,与 Redis 的基础设施紧密耦合。

(2)MySQL Router 模式:"代理式"

比喻:就像您叫了一辆专车。您每次都告诉司机(Router)您要去哪儿,由司机决定走哪条路、开哪辆车。您不需要关心后台是哪个 MySQL 实例在真正提供服务。

优点:

  • 客户端简单:客户端像使用单点 MySQL 一样使用 Router,无需感知后端集群结构。数据库的扩缩容、主从切换对客户端透明。
  • 集中控制:路由规则、负载均衡策略、故障转移都可以在 Router 层统一管理和配置。
  • 解耦:客户端与数据库集群完全解耦。

缺点:

  • 性能开销:所有流量都经过 Router,多了一次网络跳转,增加了微小的延迟,且 Router 本身可能成为性能瓶颈。
  • 引入新组件:需要额外部署、监控和维护 Router 这个高可用组件。

Redis 哨兵模式 追求的是极致的性能和简单的数据路径,愿意将复杂性放在客户端。MySQL Router 模式 追求的是客户端的简单性和架构的透明性,愿意引入一个代理层来封装所有复杂性。

在现代微服务架构中,这两种模式也很常见:服务网格类似于 Router 模式,所有流量都经过 Sidecar 代理。很多服务发现框架则类似于哨兵模式,服务实例直接相互通信。

(3)风格比较

这两种设计风格没有绝对的优劣,只有是否适合特定的场景。它们是典型的 "性能与复杂度" 和 "简单性与可控性" 之间的权衡。

为了更直观地对比这两种风格的适用场景,我们可以参考以下决策矩阵:

  • 核心权衡维度
  1. 性能 vs 延迟
  • 直连模式胜出

    • 理由:少一次网络跳转,数据路径最短。对于Redis这种对亚毫秒级延迟有极致追求的系统至关重要。

    • 代理模式短板:所有流量都经过Router,必然引入额外的延迟和潜在的瓶颈。

  1. 客户端复杂度 vs 架构简单性
  • 代理模式胜出

    • 理由:客户端无需感知后端集群变化,行为像访问单点一样简单。所有复杂性(路由、负载均衡、故障转移)都封装在代理层。

    • 直连模式短板:客户端必须集成"智能",实现服务发现、故障感知、重连逻辑,变得沉重且复杂。

  1. 可维护性与可观测性
  • 代理模式胜出

    • 理由:Router作为一个中心点,是绝佳的监控、流量控制、协议转换的切入点。可以集中实施安全策略、审计日志、限流等。

    • 直连模式短板:客户端分散,难以实施统一的管控和观测。

  1. 技术栈灵活性与耦合度
  • 代理模式胜出

    • 理由:客户端与数据库技术栈解耦。更换数据库版本甚至类型,后端集群扩缩容,对客户端完全透明。

    • 直连模式短板:客户端与特定的服务发现机制(如Redis哨兵协议)紧密耦合。

  1. 资源消耗与可扩展性
  • 直连模式胜出

    • 理由:连接是分散的,没有单点瓶颈。要服务更多客户端,只需要扩展后端资源。

    • 代理模式短板:Router本身可能成为性能和连接数的瓶颈,需要自己做集群和高可用。

(4)现代架构的演进趋势

这两种模式也在不断演进和融合。

  • Sidecar 模式(服务网格):这是两种模式的融合。每个客户端都部署一个轻量级代理(Sidecar)。对应用来说,它像是直连(localhost),非常简单。对架构来说,所有流量都经过代理,具备了集中管控的能力。实现了 "简单性" 和 "可控性" 的兼得,代价是更高的资源开销和架构复杂度。
  • 智能负载均衡器:云服务商提供的数据库服务(如AWS RDS Proxy, Azure Database for MySQL)本质上就是一个全托管的、高度可用的"Router"。它将代理的复杂性从用户侧完全移除,提供了最佳的体验。

3. 总结与选择

选择直连模式(Redis 哨兵风格)当:

  • 你对性能和数据延迟有极致要求。
  • 你的客户端技术栈统一,并且有能力维护一个健壮的客户端库。
  • 你的团队对底层基础设施有很强的掌控力。
  • 典型场景:缓存、实时排行榜、消息队列、游戏会话存储。

选择代理模式(MySQL Router 风格)当:

  • 你追求开发效率和应用侧的简单性。
  • 你的客户端技术栈多样(如多种语言、多种框架),难以统一维护客户端逻辑。
  • 你需要集中的可观测性、安全性和管控。
  • 典型场景:核心业务数据库、ERP系统、多语言微服务后端。

总而言之,这是一个经典的工程设计权衡。Redis 生于一个对性能极度渴求的时代,其核心价值是"快",因此选择了直连。MySQL 作为一个成熟稳定的 OLTP 数据库,其核心价值是"可靠"和"通用",因此选择了通过代理来保证客户端的简单和架构的清晰。

在当今云原生时代,Sidecar 模式正在尝试"鱼与熊掌兼得",但它本身也带来了新的复杂性。没有完美的银弹,只有最适合你当前和可预见未来需求的选择。

相关推荐
清静诗意1 小时前
Ubuntu Redis 安装与配置指南
linux·redis·ubuntu
T***u3332 小时前
后端缓存技术学习,Redis实战案例
redis·学习·缓存
梁萌2 小时前
缓存高可用架构-读缓存
redis·缓存·架构·高可用架构·读缓存
i***48613 小时前
Redis重大版本整理(Redis2.6-Redis7.0)
java·数据库·redis
r***86983 小时前
Redis 6.2.7安装配置
前端·数据库·redis
YQ_ZJH3 小时前
Redisson 看门狗机制详解
java·redis
Unstoppable223 小时前
八股训练营第 21 天 | Redis的数据类型有哪些?Redis是单线程的还是多线程的,为什么?说一说Redis持久化机制有哪些?
数据库·redis·缓存·八股
无心水3 小时前
【分布式利器:Redis】Redis基本原理详解:数据模型、核心特性与实战要点
数据库·redis·缓存·数据模型·i/o多路复用·redis高并发·redis基本原理
大头an3 小时前
Redis内存碎片深度解析:从动态整理到核心运维实践
数据库·redis