spring cloud gateway+nacos 服务下线感知延迟,未及时出现503,请求依然转发到下线服务

本文探讨了Spring Cloud Gateway在服务下线时感知延迟,导致请求仍被转发到已下线服务并产生500错误的情况。通过分析发现,问题可能在于Gateway的缓存未及时刷新。解决方案是扩展Nacos订阅功能,监听服务变化并刷新缓存。

spring cloud gateway服务下线感知延迟,未及时出现503

本篇算是配合之前的一篇了。整体问题是gateway对下线服务感知延迟,之前那篇文章是从服务角度解决自身注销的问题(使用undertow,服务停止后nacos下线注销延迟问题)。本篇是解决gateway自身发现服务问题。
这种问题大体就集中情况
1 服务端注销未正常运行(这个看一下nacos是否及时删除了节点信息就可以排查出来)
2 网关服务未及时发现节点的变化(这个可以在debug级别日志验证)
3 服务端和网关服务不互通

1.场景描述

注册中心使用的nacos,客户端版本1.4.1。
gateway版本3.0.1。
nacos服务下线(包含手动点下线和服务正常停机)gateway在短暂几秒内还回继续将流量转发到已下线的服务上导致500。过几秒之后恢复正常,响应码变成503。表面上看,应该是gateway服务没有及时发现服务的下线。

2.分析

日志级别调整到debug,发现通过netty发送的下线通知已经抵达gateway服务。这说明nacos注册中心和spring boot服务通讯和订阅是没问题的。
从转发的入口着手:ReactiveLoadBalancerClientFilter#choose 这个方法就是gateway转发时选择服务的

private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,
			Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {
		ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,
				ReactorServiceInstanceLoadBalancer.class);
		if (loadBalancer == null) {
			throw new NotFoundException("No loadbalancer available for " + serviceId);
		}
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
		// 最后是通过ReactorLoadBalancer的实现进行选择
		return loadBalancer.choose(lbRequest);
	}

ReactorLoadBalancer是负载均衡的接口,提供了两个实现,一个随机获取,一个轮询。
默认是使用轮询实现(RoundRobinLoadBalancer)。
RoundRobinLoadBalancer中选择服务的实现逻辑

public Mono<Response<ServiceInstance>> choose(Request request) {
		ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
				.getIfAvailable(NoopServiceInstanceListSupplier::new);
		// 在这个get方法中返回了可选服务器的集合
		return supplier.get(request).next()
				.map(serviceInstances -> processInstanceResponse(supplier
<think>我们正在处理用户的问题:在Spring Cloud Gateway 2021与Nacos的环境中,如何动态刷新负载均衡配置或实现服务实例的动态更新。 首先,我们需要明确几个关键点: 1. Spring Cloud Gateway 2021版本对应的是Spring Cloud 2021.0.x(例如2021.0.3)。 2. Nacos作为服务发现和配置中心。 3. 动态刷新的目标:负载均衡配置(例如负载均衡策略)和服务实例列表(即服务发现)。 在Spring Cloud Gateway中,负载均衡通常由Spring Cloud LoadBalancer(SC LoadBalancer)处理,它是Netflix Ribbon的替代品。在2021.0.x版本中,默认使用SC LoadBalancer。 要实现动态更新,我们需要考虑两个方面: - 服务实例列表的动态更新:当Nacos中的服务实例发生变化(上线、下线)时,Gateway如何及时感知并更新路由时的可用实例。 - 负载均衡配置的动态更新:例如更改负载均衡策略(如从轮询改为随机)而不重启网关。 ### 一、服务实例列表的动态更新 Spring Cloud Gateway通过Nacos服务发现客户端(spring-cloud-starter-alibaba-nacos-discovery)获取服务实例。Nacos客户端会定期从Nacos服务器拉取服务列表(默认间隔约10秒),同时Nacos服务器也会在服务变化时主动推送更新(通过UDP,但可能存在丢包,所以客户端也有定时拉取作为保障)。 在Spring Cloud Gateway中,我们使用`ReactiveLoadBalancer`来获取服务实例。默认情况下,`ReactiveLoadBalancer`会缓存服务实例列表,并定期更新(默认缓存时间约为35秒)。因此,从服务实例变化到Gateway感知到变化可能会有延迟。 我们可以通过调整缓存刷新时间来提高更新频率。在`application.yml`中配置: ```yaml spring: cloud: loadbalancer: cache: ttl: 5s # 设置缓存过期时间为5秒,默认是35秒 ``` 这样,负载均衡器会每5秒重新加载一次服务实例列表。 ### 二、负载均衡配置的动态更新 负载均衡配置(如策略选择)通常是在配置文件中定义的,例如: ```yaml spring: cloud: gateway: routes: - id: my_route uri: lb://my-service predicates: - Path=/my-service/** filters: - StripPrefix=1 loadbalancer: configurations: default # 可以指定负载均衡策略,例如轮询、随机等 ``` 但是,如果我们想要在运行时动态更改负载均衡策略,我们需要利用Spring Cloud的配置刷新机制。Nacos Config可以实现配置的动态刷新。 步骤: 1. 将负载均衡配置放在Nacos配置中心(例如一个Data Id为`gateway-loadbalancer.yml`的配置)。 2. 在Gateway应用中添加Nacos Config依赖和配置。 3. 使用`@RefreshScope`或配置刷新机制。 然而,需要注意的是,Spring Cloud LoadBalancer的配置(如`spring.cloud.loadbalancer.configurations`)在启动时就被加载到上下文中,目前(2021.0.x)并不支持动态刷新这些配置。也就是说,即使我们通过Nacos Config动态更改了配置,也不会立即生效。 ### 替代方案:自定义LoadBalancer配置 我们可以通过自定义`ReactiveLoadBalancer`来实现动态策略切换。基本思路: 1. 定义一个自定义的`LoadBalancer`配置类,它可以根据外部配置(例如从Nacos Config获取的配置)来创建不同的负载均衡策略。 2. 使用`@RefreshScope`结合`@Bean`来重新创建负载均衡器实例,当配置刷新时。 具体步骤: #### 1. 添加依赖 确保Gateway项目中包含以下依赖: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> ``` #### 2. 创建自定义LoadBalancer配置 创建一个自定义的负载均衡配置类,例如: ```java import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @Configuration @LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfiguration.class) public class CustomLoadBalancerConfiguration { @Bean @RefreshScope // 使这个bean可以刷新 public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); // 从配置中获取负载均衡策略,例如:spring.cloud.loadbalancer.[servicename].strategy,如果没有则使用默认策略 String strategy = environment.getProperty("spring.cloud.loadbalancer." + name + ".strategy", "round_robin"); ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class); if ("random".equals(strategy)) { return new RandomLoadBalancer(supplier, name); } else if ("round_robin".equals(strategy)) { return new RoundRobinLoadBalancer(supplier, name); } else { // 默认使用轮询 return new RoundRobinLoadBalancer(supplier, name); } } } ``` 注意:上面的`RandomLoadBalancer`和`RoundRobinLoadBalancer`需要自己实现(或者使用Spring Cloud LoadBalancer提供的,但注意Spring Cloud LoadBalancer默认提供了`RoundRobinLoadBalancer`,随机策略需要自己实现)。 #### 3. 实现随机负载均衡器 Spring Cloud LoadBalancer没有提供随机的实现,我们可以自己写一个: ```java import reactor.core.publisher.Mono; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import java.util.List; import java.util.Random; public class RandomLoadBalancer implements ReactorLoadBalancer<ServiceInstance> { private final String serviceId; private final ServiceInstanceListSupplier supplier; private final Random random; public RandomLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) { this.serviceId = serviceId; this.supplier = supplier; this.random = new Random(); } @Override public Mono<Response<ServiceInstance>> choose(Request request) { return supplier.get().next().map(instances -> { List<ServiceInstance> instanceList = instances.getInstances(); if (instanceList.isEmpty()) { return new EmptyResponse(); } int index = random.nextInt(instanceList.size()); ServiceInstance instance = instanceList.get(index); return new DefaultResponse(instance); }); } } ``` #### 4. 配置Nacos Config动态刷新 在`bootstrap.yml`(或`application.yml`)中配置Nacos Config: ```yaml spring: application: name: gateway-service cloud: nacos: discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} config: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} file-extension: yaml # 配置动态刷新 refresh-enabled: true # 指定要加载的配置 extension-configs: - data-id: gateway-loadbalancer.yml group: DEFAULT_GROUP refresh: true ``` 在Nacos配置中心创建`gateway-loadbalancer.yml`,内容可以是: ```yaml spring: cloud: loadbalancer: my-service: # 服务名 strategy: random # 或者 round_robin ``` #### 5. 测试 当我们更改Nacos中`gateway-loadbalancer.yml`的配置(例如将`my-service`的策略从`round_robin`改为`random`)时,由于我们在`ReactorLoadBalancer`的Bean上加了`@RefreshScope`,Spring Cloud会销毁这个Bean并重新创建,从而加载新的策略。 ### 注意事项 1. 动态刷新负载均衡策略可能会带来一些性能开销,因为每次刷新都会重新创建负载均衡器。 2. 服务实例列表的动态更新通过调整`spring.cloud.loadbalancer.cache.ttl`来控制,但要注意不要设置得太小,以免增加Nacos服务器的压力。 3. 确保Nacos配置的刷新能够正确触发。可以通过`/actuator/refresh`端点(需要actuator依赖)手动刷新,或者等待Nacos Config的自动刷新(默认间隔5秒)。 ### 总结 在Spring Cloud Gateway 2021与Nacos的环境中,实现负载均衡配置和服务实例的动态更新需要: - 通过调整`spring.cloud.loadbalancer.cache.ttl`来加快服务实例列表的更新。 - 通过自定义`ReactiveLoadBalancer`并结合Nacos Config的动态刷新机制,实现负载均衡策略的动态更新。 这样,我们就可以在不重启网关的情况下,动态调整负载均衡策略,并快速获取最新的服务实例列表。 ### 相关问题 1. 如何监控Spring Cloud Gateway中负载均衡器的状态? 2. 在Spring Cloud Gateway中,如何实现自定义的负载均衡算法? 3. Nacos配置动态刷新的原理是什么?</think>### Spring Cloud Gateway 2021 动态刷新负载均衡配置(Nacos环境) 在 Spring Cloud Gateway 2021.x(对应 Spring Cloud 2021.0.x)中,结合 Nacos 实现负载均衡配置和服务实例的动态更新,需要配置以下关键组件: #### 一、核心配置步骤 1. **添加必要依赖** (`pom.xml`) ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <!-- 替换Ribbon --> </dependency> ``` 2. **启用动态刷新** (`bootstrap.yml`) ```yaml spring: application: name: api-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: public # 可选命名空间 config: server-addr: 127.0.0.1:8848 refresh-enabled: true # 开启配置自动刷新 extension-configs: # 动态配置源 - data-id: gateway-routes.yml group: DEFAULT_GROUP refresh: true # 关键:允许动态刷新 - data-id: loadbalancer-config.yml group: DEFAULT_GROUP refresh: true ``` 3. **Nacos 负载均衡配置** (`loadbalancer-config.yml`) ```yaml spring: cloud: loadbalancer: cache: ttl: 5s # 缩短实例缓存时间(默认35s) configurations: round-robin # 负载均衡策略 ``` 4. **动态路由配置** (`gateway-routes.yml`) ```yaml spring: cloud: gateway: routes: - id: user-service uri: lb://user-service # lb://启用负载均衡 predicates: - Path=/api/users/** ``` #### 二、实现动态更新机制 1. **服务实例自动更新** Nacos 客户端默认每 10 秒主动拉取服务列表,服务变更时通过 UDP 推送更新。Gateway 通过 `CacheManager` 控制刷新频率: ```yaml spring: cloud: loadbalancer: cache: ttl: 3s # 建议3-5秒(生产环境平衡性能与实时性) ``` 2. **负载均衡策略热更新** 创建自定义 `LoadBalancer` 配置类: ```java @Configuration @RefreshScope // 启用配置刷新 public class LoadBalancerConfig { @Bean @RefreshScope public ReactorLoadBalancer<ServiceInstance> customLoadBalancer( Environment env, LoadBalancerClientFactory factory) { String serviceId = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RoundRobinLoadBalancer( // 可从Nacos配置动态读取策略 factory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class), serviceId ); } } ``` 3. **监听配置变更事件** 在需要即时响应的场景,添加事件监听器: ```java @Component public class NacosRefreshListener { @EventListener public void handleRefresh(RefreshScopeRefreshedEvent event) { // 重新初始化负载均衡器 LoadBalancerCacheManager.evictAll(); } } ``` #### 三、验证动态更新 1. **测试服务实例更新** - 在 Nacos 控制台下线某个服务实例 - 观察 Gateway 日志:`Refreshing ServiceInstance list for service: user-service` - 请求网关验证流量是否转移到健康实例 2. **测试配置更新** - 在 Nacos 修改 `loadbalancer-config.yml`: ```yaml spring: cloud: loadbalancer: configurations: random # 切换为随机策略 ``` - 查看网关日志:`Refreshing LoadBalancer configuration` #### 四、注意事项 1. **性能权衡** 缓存时间 `ttl` 过短会增加 Nacos 压力,建议: - 测试环境:1-3 秒 - 生产环境:5-10 秒 2. **版本兼容性** | 组件 | 推荐版本 | |------|----------| | Spring Cloud | 2021.0.3+ | | Nacos Client | 2.1.0+ | | Spring Cloud Alibaba | 2021.0.4.0+ | 3. **健康检查强化** 在 `application.yml` 添加: ```yaml spring: cloud: nacos: discovery: health-check-enabled: true # 启用健康检查 heart-beat-interval: 5s # 心跳间隔 ``` > **实现原理**:Nacos 配置变更时,通过 `RefreshEvent` 触发 `LoadBalancerCacheManager` 缓存清除,下次请求重新加载最新配置和服务实例[^1]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值