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

  • 1.场景描述
  • 2.分析
    • 2.1定位问题
  • 3.解决方案

本篇算是配合之前的一篇了。整体问题是gateway对下线服务感知延迟,之前那篇文章是从服务角度解决自身注销的问题(使用undertow,服务停止后nacos下线注销延迟问题)。本篇是解决gateway自身发现服务问题。

1.场景描述

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

2.分析

如果遇到这种问题,可以先排查一下面几种情况在尝试解决
1 服务端注销未正常运行(这个看一下nacos是否及时删除了节点信息就可以排查出来)
2 网关服务未及时发现节点的变化(这个可以在debug级别日志验证)
3 服务端和网关服务不互通

日志级别调整到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, serviceInstances));}

上面那个get的实现是:CachingServiceInstanceListSupplier#CachingServiceInstanceListSupplier这个类中提供的

public CachingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, CacheManager cacheManager) {super(delegate);this.serviceInstances = CacheFlux.lookup(key -> {// 这里发现有缓存!感觉目的地近了。Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);....}).then());}

2.1定位问题

调试一下看看:

  • 服务A启动注册到nacos
  • gateway正常将/test/hello转发至服务A
  • 在nacos管理端让服务A下线
  • 立刻访问不停/test/hello
  • 最初几秒内发现gateway还是会把流量打到服务A
  • 之后正常响应503

在获取服务集群信息的地方打断点

public CachingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, CacheManager cacheManager) {super(delegate);this.serviceInstances = CacheFlux.lookup(key -> {// TODO: configurable cache nameCache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);if (cache == null) {if (log.isErrorEnabled()) {log.error("Unable to find cache: " + SERVICE_INSTANCE_CACHE_NAME);}return Mono.empty();}// 在异常的时间段,这个list还是有信息。集合没内容之后开始响应503List<ServiceInstance> list = cache.get(key, List.class);if (list == null || list.isEmpty()) {return Mono.empty();}return Flux.just(list).materialize().collectList();}...}

看来是这个缓存没有及时刷新的原因!后续找了一段时间,没找到刷新缓存的地方就放弃了。还是用笨方法先解决吧

3.解决方案

已经知道了问题所在,想办法解决就是了。
整体思路:在订阅nacos服务变化中进行功能拓展,刷新缓存。

三个类:
MySubscribeConfig:进行订阅配置的入口
MyNacosSubscribe:订阅nacos的实现,用来发布订阅消息
MyNacosEventListener:消息处理的实现,在这里刷新缓存

先在写个spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.dong.gateway.config.MySubscribeConfig

MySubscribeConfig:


import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.dong.common.core.util.SpringContextHolder;
import com.dong.server.gateway.subscribe.MyNacosSubscribe;
import com.dong.server.gateway.subscribe.MyNacosEventListener;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 首先订阅当前网关关注的服务* nacos服务更新通知,但是gateway有一套自己的服务缓存列表。每次接到通知更新不及时导致转发到已经下线的服务* gateway获取缓存参考:org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier* nacos订阅参考:com.alibaba.cloud.nacos.discovery.NacosWatch#start()**/
@Configuration
@Import(SpringContextHolder.class)
@AutoConfigureAfter(LoadBalancerCacheAutoConfiguration.class)
public class MySubscribeConfig {@Beanpublic MyNacosSubscribe getMyNacosSubscribe(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties properties){LoadBalancerCacheManager cacheManager = SpringContextHolder.getBean(LoadBalancerCacheManager.class);return new MyNacosSubscribe(nacosServiceManager,properties,new MyNacosEventListener(loadBalancerCacheManager));}
}

MyNacosSubscribe

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.naming.NamingService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;/*** 订阅nacos推送更新事件* 启动和加载路由时重新订阅*/
@Slf4j
@AllArgsConstructor
public class MyNacosSubscribe implements ApplicationRunner {private NacosServiceManager nacosServiceManager;private NacosDiscoveryProperties properties;private MyNacosEventListener myEventListener;private Set<String> getRouteServices(){// TODO 这里返回自己要订阅的服务名称return new HashSet();}// 这里监听时间是为了在路由信息修改时候,重新订阅服务的。如果说路由重新加载不会有订阅变动的话,可以去掉
@org.springframework.context.event.EventListener({RefreshRoutesEvent.class})public void subscribe() {NamingService namingService = nacosServiceManager.getNamingService(properties.getNacosProperties());try {Set<String> services = getRouteServices();if(CollectionUtil.isNotEmpty(services)){for (String service : services) {namingService.subscribe(service, properties.getGroup(),null, myEventListener);}}} catch (Exception e) {log.error("namingService subscribe failed, properties:{}", properties, e);}}@Overridepublic void run(ApplicationArguments args) throws Exception {subscribe();}}

MyNacosEventListener

import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.naming.listener.Event;
import com.alibaba.nacos.api.naming.listener.EventListener;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;import java.util.List;/*** 处理nacos推送更新事件*/
@Slf4j
@AllArgsConstructor
public class MyNacosEventListener implements EventListener {private LoadBalancerCacheManager loadBalancerCacheManager;@Overridepublic void onEvent(Event event) {try {if (event instanceof NamingEvent) {Cache cache = loadBalancerCacheManager.getCache(CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME);if(cache!=null){NamingEvent namingEvent = ((NamingEvent) event);String serviceName = namingEvent.getServiceName();String[] split = serviceName.split(Constants.SERVICE_INFO_SPLITER);String serviceId = split[1];log.debug("收到更新服务消息:{}",serviceId);List<Instance> instances = namingEvent.getInstances();cache.put(serviceId,  NacosServiceDiscovery.hostToServiceInstanceList(instances,serviceId));}}}catch (Exception e){log.error("处理nacos推送失败!",e);}}
}

20220620更新:MySubscribeConfig中用到的工具类

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;/*** spring工具类*/
@Slf4j
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {private static ApplicationContext applicationContext = null;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {SpringContextHolder.applicationContext = applicationContext;}public static <T> T getBean(Class<T> requiredType) {return applicationContext.getBean(requiredType);}public static void clearHolder() {if (log.isDebugEnabled()) {log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);}applicationContext = null;}@Overridepublic void destroy() {SpringContextHolder.clearHolder();}}

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

  1. spring cloud gateway nacos搭建动态路由

    spring cloud gateway nacos搭建动态路由 一.环境 开发工具:IntelliJ Idea JDK 1.8 Spring boot 2.3.12.RELEASE spring c ...

  2. 前后端分离,解决Spring Cloud GateWay + Nacos使用lb请求报503错误以及403跨域问题

    在使用前后端分离时,前端请求gateway接口,gateway使用lb负载均衡请求目标服务. 如下: spring:cloud:gateway:routes:- id: test-demouri: l ...

  3. spring cloud gateway nacos 网关设置

    前期准备:可参考上一篇,spring cloud nacos 服务提供.服务消费(Feign.RestTemplate)及Hystrix熔断设置 1.下载安装 Nacos 服务中心(下载地址 http ...

  4. Spring Cloud Gateway 自定义网络响应状态码(401,500,503等等)

    近几天打算做一个网关校验的东西,但是发现在使用Gateway的过滤器的时候,没找到设置响应的状态码,网上找了好多也没找到emmmm,不过还好解决了. 直接贴上代码,下面有给出: package com ...

  5. nacos oaut服务地址_用户认证的例子:Spring Security oAuth2 + Spring Cloud Gateway + Nacos + Dubbo...

    这个例子是商城后台项目的一部分,主要使用了oAuth2的密码模式完成用户名密码认证功能.主要流程是:使用Nacos作为注册中心,操作用户的服务user-mgr-service作为服务提供者,注册到Na ...

  6. 微服务网关终结者?Spring Cloud推出新成员Spring Cloud Gateway

    导语:Spring Cloud Gateway基于Spring Boot 2,该项目提供了一个构建在Spring 生态之上的 API 网关.Spring Cloud Gateway旨在提供一种简单而有 ...

  7. 网关服务Spring Cloud Gateway(三)

    上篇文章介绍了 Gataway 和注册中心的使用,以及 Gataway 中 Filter 的基本使用,这篇文章我们将继续介绍 Filter 的一些常用功能. 修改请求路径的过滤器 StripPrefi ...

  8. spring cloud gateway网关和链路监控

    文章目录 目录 文章目录 前言 一.网关 1.1 gateway介绍 1.2 如何使用gateway 1.3 网关优化 1.4自定义断言和过滤器 1.4.1 自定义断言 二.Sleuth--链路追踪 ...

  9. Spring cloud gateway 详解和配置使用

    spring cloud gateway 介绍 1. 网关是怎么演化来的 单体应用拆分成多个服务后,对外需要一个统一入口,解耦客户端与内部服务 注:图片来自网络 2. 网关的基本功能 网关核心功能是路 ...

最新文章

  1. YOLO系列:YOLO v1深度解析
  2. 【Flutter】Dart 数据类型 字符串类型 ( 字符串定义 | 字符串拼接 | 字符串 API 调用 )
  3. c++图形化界面_还能这样用?Linux下如何编译C程序?
  4. 菜鸟之2011-2012学年总结
  5. 中国科学家突破二氧化碳人工合成淀粉技术
  6. (3)二分频systemverilog与VHDL编码
  7. 【经验心得】固定布局做到各手机屏幕适配简单粗暴的方法
  8. mac 未能启动java jar_在Mac上运行.jar
  9. Hive安装详细步骤
  10. 沉浸式视听体验:全景声技术是如何实现的?
  11. 腾讯企业邮箱管理权限可实现什么?
  12. 基于JAVA网上汽车售票系统计算机毕业设计源码+数据库+lw文档+系统+部署
  13. python 卡方分布值_python数据分析探索变量之间的关系
  14. 计算机小游戏有哪些,4399电脑小游戏中有一个和lol类似的游戏叫什么
  15. xLua热更新(一)xLua基本使用
  16. 多道批处理系统的调度
  17. 【人工智能】技术采纳:重新思考医疗保健的罗杰钟形曲线
  18. CentOS关机与重启命令
  19. 伴侣新生自动聊天源码分享
  20. 光中断器行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)

热门文章

  1. 又到一年表白季,520它又来了!Python花式表白的几种姿势
  2. mysql日期加天_MySql日期加天数,小时,分钟...得到新的时间
  3. android 逆向工程
  4. np.pad()详解
  5. 智能盘点 - 钢筋数量 AI 识别 - 数据下载与评测
  6. 当没有成熟案例可参考时,企业该如何实现数字化转型?
  7. Mac 键盘符号说明与快捷键大全
  8. 详解型号/版本号/序列号/注册码
  9. ComponentOne Crack,componentone控件集
  10. 【腾讯犀牛鸟开源人才培养计划】萌新的开源心得之Omi框架