当前位置: 首页 > news >正文

微服务高可用流程讲解

如何理解从前端nginx到后端微服务高可用架构问题,下面从nginxgatewaynacos各个服务节点的角度讲解下应该如何进行高可用,比如nginx是前端向后端进行的负载均衡,也相当于均衡地向各个gateway网关进行请求,再由gateway网关拉取注册在nacos的服务,均衡地分发请求。

下面我将从前端到后端,逐一拆解每个组件在高可用架构中的角色和协作方式。

一个更完整的高可用架构如下图所示:
在这里插入图片描述

1. 前端 (Frontend) -> Nginx (负载均衡器)

Nginx 在这里的作用是反向代理和负载均衡。

高可用实践

Nginx本身高可用:单台 Nginx 是单点,所以需要主从备份。通常使用 Keepalived 方案,为主从两台 Nginx 分配一个虚拟IP (VIP)。客户端(浏览器、App)只访问这个VIP。当主Nginx宕机时,Keepalived会自动将VIP漂移到从Nginx,实现无缝切换。更现代的方案是直接使用云厂商的负载均衡器(SLB/CLB/ALB),它们自身就是高可用的。

作用: 将前端所有请求均匀地(如轮询、加权、最少连接等策略)分发到后端的多个 Gateway 实例上。如果某个Gateway实例宕机,Nginx会自动将其从健康检查中剔除,不再转发流量。

2. Nginx -> Spring Cloud Gateway (API 网关)

Gateway 是统一的流量入口,负责路由、鉴权、限流、熔断等。

高可用实践

Gateway多实例部署:你需要启动多个完全相同的 Gateway 实例(比如在K8s上部署3个Pod,或在多台服务器上部署3个Jar包)。

无状态化: Gateway 实例本身不能保存状态(如Session)。所有实例都是平等的,任何一台处理请求的结果都一样。这是水平扩展的前提。

服务注册: 所有这些Gateway实例都会把自己注册到Nacos集群(注意:是集群,不是单点)上,服务名通常是 gateway-service。

3. Gateway & 服务节点 -> Nacos (服务发现中心)

高可用实践

Nacos集群部署: Nacos 本身必须以集群模式(通常至少3个节点) 部署,共享同一个高可用的MySQL数据库(或其它持久化存储)。这样,任何一个Nacos节点挂掉,其他节点依然能提供服务,数据也不会丢失。

客户端配置: Gateway 和所有微服务节点在配置Nacos地址时,不能只配一个,必须配置整个Nacos集群的地址列表。

# 在Gateway和所有微服务的application.yml中
spring:cloud:nacos:discovery:server-addr: 192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848config:server-addr: 192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848

工作原理:

客户端(Gateway或服务)启动时,会随机或按顺序尝试连接这个列表中的某个Nacos节点。

一旦连接成功,客户端会与Nacos集群建立心跳和长连接,进行服务注册、订阅和配置拉取。

Nacos集群内部通过Raft协议保证数据一致性。任何一个节点收到的注册信息,都会同步到其他节点。

因此,Gateway 无论连接到 Nacos 集群中的哪一个节点,都能获取到全量的、一致的服务列表。它据此来做本地负载均衡(默认是RoundRobin轮询)。

4. Gateway -> 各个服务节点 (Service Instances)

Gateway 从 Nacos 获取到健康的服务实例列表后,会根据预设的路由规则,将请求转发到具体的某个服务实例上(如 user-service 的 192.168.1.201:8080)。

高可用实践

服务多实例部署:这是最基本的要求。每个微服务(如 user-service, order-service)都必须有多个实例部署在不同的服务器或容器中。

健康检查:Nacos 客户端会定期向Server发送心跳,Nacos Server也会主动检查客户端健康状态。如果某个服务实例宕机,Nacos会在很短时间内(秒级)将其标记为不健康并从服务列表中剔除。Gateway 会定时从Nacos拉取最新的服务列表,因此它不会再把请求发往已经宕机的实例。

客户端负载均衡:Gateway 在转发时,会使用 LoadBalancerClient(如Spring Cloud LoadBalancer)在本地服务列表中进行负载均衡(如轮询),而不是每次都请求Nacos。这大大提高了性能并降低了Nacos的压力。

5. 各个服务节点 -> 下游依赖 (数据库、缓存等)

高可用实践

微服务本身高可用了吗?还不够。如果它们依赖的数据库、Redis、MQ等是单点的,那整个系统依然脆弱。

数据层高可用:必须使用数据库主从复制、读写分离集群(如MySQL MGR、Redis Sentinel/Cluster、RabbitMQ镜像队列等)。这样即使一个数据库节点宕机,整个系统仍可提供只读服务或通过切换继续服务。

总结与梳理:一次完整的请求流

  • 用户 访问 api.yourcompany.com (VIP)。

  • Nginx (SLB) 接收到请求,根据负载均衡算法,将其转发到一台健康的 Gateway 实例上。

  • Gateway 接收到请求,解析路径,确定要访问的服务(如 /user/api/1 -> user-service)。

  • Gateway 查看其本地的服务列表(这个列表是它从连接的Nacos集群节点那里定时拉取并缓存的),通过负载均衡器选出一个健康的 user-service 实例地址(如 192.168.1.201:8080)。

  • Gateway 将请求转发给该 user-service 实例。

  • user-service 实例处理请求,期间可能需要调用其他服务(通过Nacos发现)或访问高可用的数据库集群。

  • 处理完成后,响应结果原路返回给用户。

高可用就是在你这个思路的每一个环节上都去掉“单点”,通过多实例+负载均衡+健康检查的方式,让整个链条上的任何一个环节宕机都不会导致全局故障。

问题一

如果nginx负载均衡给了gateway,gateway也负载均衡给了各个服务节点,请求是如何分发的?如:
nginx->g1->s1(第一次),nginx->g2->s2(第二次),nginx->g3->s3(第三次)

Gateway 在转发请求时,默认的负载均衡策略是轮询(RoundRobin),并且它的负载均衡对象是它所连接的所有健康实例。

详细流程分解
我们假设所有组件都已启动并注册完毕,状态健康。

  1. 初始状态:服务注册
  • 三个 user-service 实例 s1, s2, s3 启动,并向 Nacos 集群 注册自己。

  • 三个 gateway 实例 g1, g2, g3 启动,也向 Nacos 集群 注册自己,并从 Nacos 获取到了 user-service 的完整实例列表 [s1, s2, s3]。

  1. 请求流程
  • 第一次请求:

    • 用户请求到达 Nginx。

    • Nginx 根据其负载均衡策略(比如默认的轮询),从 [g1, g2, g3] 中选择了 g1,将请求转发给它。

    • g1 接收到请求,解析后发现需要路由到 user-service。

    • g1 查看自己本地缓存的、从Nacos获取的服务列表 [s1, s2, s3]。

    • g1 使用其内置的负载均衡器(默认轮询),从列表中选择第一个实例,比如 s1,将请求转发给 s1。

    • 路径:Nginx -> g1 -> s1

  • 第二次请求:

    • 用户的下一个请求到达 Nginx。

    • Nginx 继续轮询,这次从 [g1, g2, g3] 中选择了 g2,将请求转发给它。

    • g2 接收到请求,同样需要路由到 user-service。

    • g2 也拥有完整的服务列表 [s1, s2, s3]。

    • g2 的负载均衡器独立工作,它也会使用轮询策略。但关键点在于:g2 的负载均衡器有自己的轮询计数器,它与 g1 的计数器是独立的、不共享的。

    • 因此,g2 的负载均衡器也会从 [s1, s2, s3] 中选择第一个实例,即 s1。

    • 路径:Nginx -> g2 -> s1

  • 第三次请求:

    • Nginx 轮询到 g3。

    • g3 独立地轮询,选择 s1。

    • 路径:Nginx -> g3 -> s1

  • 第四次请求:

    • Nginx 又轮询回 g1。

    • 此时,g1 的负载均衡器会选择列表中的下一个实例,即 s2。

    • 路径:Nginx -> g1 -> s2

以此类推。

这是一个两级负载均衡系统。

第一级(Nginx):在 Gateway 实例 之间进行负载均衡。

第二级(Spring Cloud Gateway):在每个 Gateway 实例内部,针对目标服务实例进行负载均衡。

负载均衡器独立性:每个 Gateway 实例内部的负载均衡器都是独立工作的,它们之间不会同步“上次选到了哪个实例”这种状态信息。因为它们的设计是无状态的。

最终分布:从全局来看,如果所有组件都使用默认的轮询策略,并且请求量足够大,那么请求会均匀地分布到所有的服务实例(s1, s2, s3) 上。虽然某几个连续的请求可能碰巧都落到了 s1 上,但长期来看是均衡的。

唯一的偏差是忽略了每个Gateway内部的负载均衡器是独立工作的,导致最初的几个请求可能不会像你想象的那样完美地轮询到不同的服务实例。

但在大规模并发下,这种独立性和无状态设计正是保证系统可扩展性和高可用的关键。

问题二

如果Gateway 实例本身不能保存状态(如Session),那gateway是怎么能做到负载均衡的

这触及了无状态服务和负载均衡的核心机制。不过既然是无状态的,它怎么“记住”该轮询到哪个节点呢?

关键在于:负载均衡不需要“记住”长期状态,它只需要一个非常简单的、短暂的“指针”即可。而这个指针(通常只是一个原子整数)是保存在每个Gateway实例的内存中的,不需要共享。

下面来详细解释几种常见的负载均衡策略是如何在无状态的Gateway中工作的:

1. 轮询 (Round Robin) - 默认策略
这是最常用的策略。

工作原理: 每个Gateway实例内部都维护一个本地原子计数器 (Atomic Integer),初始值为0。

过程:

  • 当请求到达Gateway实例(例如g1)时,它要决定转发到哪个服务实例(例如user-service的s1, s2, s3)。

  • g1的负载均衡器会从本地缓存的服务列表[s1, s2, s3]中,根据当前计数器的值取模运算来选择实例:index = counter++ % instanceList.size()。

  • 假设计数器当前是0:0 % 3 = 0 -> 选择s1。

    • 下一个请求到来,计数器变为1:1 % 3 = 1 -> 选择s2。

    • 再下一个,计数器变为2:2 % 3 = 2 -> 选择s3。

    • 再下一个,计数器变为3:3 % 3 = 0 -> 又选择s1。

为什么这是“无状态”的?

  • 这个计数器只存在于g1实例的内存中。它不关心其他Gateway实例(g2, g3)的计数器是多少。
  • 它不关心之前是谁处理了哪个用户的请求。每一个新请求对它来说都是独立的,它只是简单地根据本地计数器+1,然后选择。
  • 如果g1实例重启,计数器重置为0,负载均衡会从头开始,但这不影响系统的正确性,只是打破了均匀性,很快又会恢复。

这就解释了之前的例子:为什么连续两个请求nginx->g1->s1和nginx->g2->s1,两个Gateway都选择了s1。因为g1和g2的计数器是独立的,并且都从0开始。

2. 随机 (Random)

工作原理: 更简单。每次有请求需要转发时,直接在健康的服务实例列表中随机选择一个。

无状态性: 完全不需要任何记录。每次选择都是一个独立事件。在大流量下,结果也是均匀分布的。

3. 最少连接数 (Least Connections) - 需要轻微“状态”

这个策略需要知道每个服务实例当前正在处理的连接数,它似乎需要“状态”。

工作原理: 选择当前处理连接数最少的服务实例。

无状态性: 这里的“状态”是瞬时状态,而不是会话状态。

Gateway会通过心跳或定时拉取从注册中心(如Nacos)获取每个服务实例的当前健康状态和元数据(其中可以包含近似的连接数信息)。

或者,Gateway的负载均衡器自己内部为一个服务实例列表维护一个非常简单的、本地的连接数计数器(例如,每转发一个请求给s1,就给s1的计数器+1,收到响应后就-1)。

这个“状态”仍然是本地的、短暂的。它存在于单个Gateway实例的内存中,用于做下一次选择的决策。如果这个Gateway实例宕机,这个状态就消失了,但无伤大雅,新上线的实例会重新开始计数。它不需要被持久化,也不需要在不同Gateway实例间共享。

问题三

如果Gateway 在转发时,会使用 LoadBalancerClient在本地服务列表中进行负载均衡(如轮询),而不是每次都请求Nacos。那么当服务A某个实例A1宕机了,gateway怎么知道这个实例A1宕机了呢

这是一个非常关键的问题,它直接关系到微服务架构的可靠性和实时性。Gateway 并不是神机妙算,它能知道 A1 宕机,依赖于一套由 Nacos 和 客户端 共同协作的服务健康检查机制。

整个过程可以概括为:“Nacos 负责发现和通知,Gateway 负责更新本地列表”。

下面是详细的机制分解:

服务健康检查 (Health Check)

Nacos 通过两种主要方式来探测服务实例的健康状态:

客户端心跳上报 (Client Beat) - 主要方式

工作原理: 当服务实例 A1、A2、A3 启动并成功注册到 Nacos 后,它们会定期(例如每5秒) 向 Nacos Server 发送一个心跳包,说:“我还活着!”

故障判断: 如果 Nacos Server 在一定时间(例如15秒) 内没有收到来自 A1 的心跳,它就会将 A1 实例的状态标记为“不健康”(healthy = false)。如果再经过一段时间(例如30秒)还没收到心跳,Nacos 会直接将这个实例从注册列表中剔除。

服务端主动探测 (Server Probe) - 补充方式

工作原理: Nacos Server 也可以主动发起检查。例如,它可能会尝试调用服务实例 A1 的一个健康检查接口(如 /actuator/health)。

故障判断: 如果连续几次调用失败或超时,Nacos Server 就会判定该实例不健康。

第一种方式是默认且最常用的。

Gateway 如何获取到最新的服务列表?

现在,Nacos 已经知道 A1 宕机了,但它需要把这个消息告诉所有关心 service-A 的消费者,也就是 Gateway。这个过程是通过 “订阅-推送”机制 实现的。

订阅 (Subscribe):

当 Gateway(g1, g2, g3)启动时,它向 Nacos 注册自己的同时,也会订阅它所关心的服务(如 service-A)的变更。

Gateway 与 Nacos Server 之间维护着一个长连接。

推送 (Push) / 拉取 (Pull):

理想模式(推送): 当 Nacos 检测到 service-A 的实例列表发生变化(如 A1 被剔除),它会立即通过长连接主动推送这个变更事件给所有订阅了 service-A 的 Gateway 实例。

兜底模式(拉取): 同时,Gateway 客户端也会定时(例如每10秒) 主动向 Nacos 拉取一次最新的服务列表,作为一个补偿和备份机制,防止推送消息丢失。

更新本地缓存:

Gateway 在收到推送消息或拉取到新列表后,会立即更新其内存中缓存的 service-A 的服务实例列表。旧的列表 [A1, A2, A3] 会被更新为 [A2, A3]。

此后,Gateway 的 LoadBalancerClient 在进行负载均衡选择时,只会从健康的、最新的列表 [A2, A3] 中进行选择。 它再也“看不见”已经宕机的 A1 了,因此绝对不会再把请求转发给它。

所以,Gateway 能知道实例宕机,并不是因为它自己能探测到,而是因为它信任并依赖于 Nacos 这个“服务大脑”来告诉它谁才是健康的。 这种设计解耦了各个组件的职责,使得系统更加清晰和可扩展。

延迟说明: 这个过程不是瞬时的,会有几秒到几十秒的延迟(默认配置下通常在15-30秒)。这是为了权衡网络抖动和实时性做出的设计。对于绝大多数业务场景,这个延迟是可接受的。如果需要更高的敏感性,可以调小心跳和超时的间隔,但这会增加Nacos的压力和网络开销。

问题四

那么gateway是怎么订阅nacos的服务的

Gateway(或者说其底层的Spring Cloud LoadBalancer)是通过 “事件驱动” 和 “长连接推送” 相结合的方式来订阅Nacos服务的。

整个过程的核心是 NacosServiceDiscovery 和 NacosWatch 这两个组件。

详细的订阅与通知流程

1. 启动时:初始拉取与订阅

当你的Gateway应用启动时,发生了以下事情:

注册与发现客户端初始化:

由于你引入了 spring-cloud-starter-alibaba-nacos-discovery 依赖,Spring Cloud 会自动配置 NacosServiceDiscovery 客户端。

这个客户端会读取配置文件中 spring.cloud.nacos.discovery.server-addr 的地址,与Nacos服务器集群建立连接。

获取初始服务列表:

Gateway在需要路由到某个服务(例如 user-service)之前,NacosServiceDiscovery 会先调用 getInstances(serviceId) 方法。

该方法会直接向Nacos服务器发起一次HTTP查询请求,获取到 user-service 所有健康的实例列表(例如 [A1, A2, A3]),并缓存在Gateway的本地内存中。

发起订阅(Subscribe):

在获取到初始列表后,NacosWatch 组件会代表Gateway向Nacos服务器发起一个订阅。

这个订阅本质上是一个“监听”请求,它告诉Nacos服务器:“我是Gateway,我关心 user-service 这个服务,如果它的实例列表有任何变化(增、删、改),请立刻通知我。”

为了实现实时通知,Gateway会和Nacos服务器建立一个UDP或HTTP长连接(具体取决于Nacos的版本和配置),作为推送数据的通道。

2. 运行时:事件推送与本地更新

当Nacos注册中心的服务列表发生变化时(例如实例A1宕机):

Nacos检测到变化:Nacos服务器通过心跳超时机制,发现 user-service 的实例A1宕机,并将其从注册列表中剔除。

Nacos主动推送事件:由于Gateway之前已经订阅了 user-service,Nacos服务器会立刻通过之前建立的长连接通道,向Gateway推送一个事件通知。这个通知非常简单,只包含基本的服务名等元数据,内容是:“注意!user-service 的服务列表变了!”。

Gateway触发更新回调:Gateway端的Nacos客户端收到这个推送事件后,会触发一个事先注册好的回调函数(Callback)。

拉取最新列表并更新缓存:

这个回调函数不会直接相信推送消息里的具体内容,而是会再次调用 NacosServiceDiscovery.getInstances(serviceId) 方法。

该方法会再次查询Nacos服务器,获取到 user-service 最新的、完整的实例列表(现在是 [A2, A3])。

最终,这个全新的列表会覆盖掉Gateway本地内存中缓存的旧列表。

负载均衡器生效:此后,所有新进来的请求,其负载均衡(如LoadBalancerClient)都会基于这个最新的、正确的列表 [A2, A3] 来进行,从而避免了将请求发往已经宕机的A1实例。

为什么是“推送+拉取”模式?

你可能会问,为什么Nacos不直接把最新的列表推过来,而是要让Gateway再拉取一次?

这是一种非常经典的设计,权衡了可靠性和数据量:

可靠性: 推送消息可能因为网络问题而丢失。如果只依赖推送,Gateway可能会漏掉变更。而让客户端在收到推送后主动拉取一次,可以确保最终拿到的一定是最准确的数据。

数据量: 服务列表可能很大。直接推送整个实例列表数据包会很大,占用网络带宽。而只推送一个简单的“有变化”的通知,让客户端自己去拉取,网络效率更高。

代码层面如何体现?
虽然你看不到完整的流程,但在你的Gateway项目中,最相关的配置和代码逻辑是:

配置中心:你的 application.yml 中配置了Nacos服务器地址,这是一切的基础。

spring:cloud:nacos:discovery:server-addr: localhost:8848

路由配置:你在配置路由时指定的 uri: lb://user-service,其中的 lb 协议就是触发上述所有流程的开关。它告诉Gateway这个路由需要用到负载均衡,需要去发现服务名为 user-service 的实例。


文章转载自:

http://QvE7sYHB.gwxsk.cn
http://3ru68aAi.gwxsk.cn
http://ZlaI5Ige.gwxsk.cn
http://jH3bgX0I.gwxsk.cn
http://kluLjNo4.gwxsk.cn
http://O3W8o7Eg.gwxsk.cn
http://nBnS5j0n.gwxsk.cn
http://X5rpeC2D.gwxsk.cn
http://jQEvpCfS.gwxsk.cn
http://2viouOLZ.gwxsk.cn
http://rE39rfpc.gwxsk.cn
http://rqmnjyDt.gwxsk.cn
http://C4feCnXz.gwxsk.cn
http://BsKuff9f.gwxsk.cn
http://oyUS7wo9.gwxsk.cn
http://E0k0lERh.gwxsk.cn
http://mYrIGO1P.gwxsk.cn
http://0r5S1TIk.gwxsk.cn
http://fV58cY0S.gwxsk.cn
http://gkhrZrPp.gwxsk.cn
http://90Afs7nx.gwxsk.cn
http://TFkmJa7I.gwxsk.cn
http://DkGzegJB.gwxsk.cn
http://Qb831hKy.gwxsk.cn
http://WyKh7uxT.gwxsk.cn
http://KdVeIy9h.gwxsk.cn
http://o4Uo4c4v.gwxsk.cn
http://oqJr6cEh.gwxsk.cn
http://YKK1v0Hr.gwxsk.cn
http://ZYDIq5rM.gwxsk.cn
http://www.dtcms.com/a/388655.html

相关文章:

  • 云HIS系统,HIS源码,基于云计算技术的医院信息管理平台,采用B/S架构和微服务技术开发,支持SaaS应用模式。
  • 【卷积神经网络详解与实例】10——经典CNN之GoogLeNet
  • C# 委托和事件详解,委托 vs 方法封装解析
  • MariaDB源码编译安装
  • 多智能体编排之王:深度解析微软Semantic Kernel的AgentOrchestration架构革命
  • AI工具推荐之ezremove.ai
  • 关于Address Editor中修改基地址和地址空间的指南
  • 【Linux 系统探幽:从入门到内核・系统编程开篇】基础指令与权限精讲,筑牢系统开发根基
  • 【STL库】哈希封装 unordered_map/unordered_set
  • 【AI编程】Qoder AI 编程工具从部署到深度使用实战详解
  • 网络原理——数据链路层
  • 大语言模型的 “幻觉” 难题:技术成因、解决方案与应用风险规避
  • 状态保留功耗门控 SRPG (State Retention Power Gating)
  • Elman神经网络多输入多输出回归预测+SHAP可解释分析+新数据预测(MATLAB源码)
  • 408 王道数据结构的学习记录
  • 使用内存映射读取文件和写入文件,并进行性能测试
  • SQL的UNION用法大全介绍
  • 从Web原生到高性能:如何优化企业数据库管理工具
  • 基于python新能源汽车数据分析可视化系统 懂车帝 Scrapy爬虫 Django框架 Vue框架 大数据项目(源码+文档)✅
  • 线性回归和 softmax 回归
  • mysql远程访问连接设置
  • 《WINDOWS 环境下32位汇编语言程序设计》学习17章 PE文件(2)
  • Linux网络编程:从协议到实战
  • Vector 底层实现详解
  • OpenShift Virtualization - 虚机存储的相关概念 DataVolume、CDI 和 StorageProfile
  • 2025年Web自动化测试与Selenium面试题收集:从基础到进阶的全方位解析
  • pytorch中的FSDP
  • 贪心算法与材料切割问题详解
  • 2. 结构体
  • MySQL 核心操作:多表联合查询与数据库备份恢复