Spring Cloud Gateway + Nacos 实现服务上下线无缝切换

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: Spring Cloud Gateway + Nacos 实现服务上下线无缝切换

大家好,我是不才陈某~

最近知识星球的球友在学习星球中的《精尽Spring Cloud Alibaba》专栏提到一个问题,相信也有很多人在线上环境遇到过,或许也因此被批过:一个集群中有某个服务突然下线,但是网关还是会去请求这个实例,所以线上就报错了,报错信息如下图:

究其原因到底为何呢?有没有一种靠谱的解决方案呢?别着急,往下看

产生原因

Gateway中有个缓存 CachingRouteLocator ,而网关服务使用的是lb模式,服务在上线或者下线之后,未能及时刷新这个缓存,相应的源码如下:

public class CachingRouteLocator implements Ordered, RouteLocator,
  ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {
 private static final Log log = LogFactory.getLog(CachingRouteLocator.class);
 private static final String CACHE_KEY = "routes";
 private final RouteLocator delegate;
 private final Flux<Route> routes;
 private final Map<String, List> cache = new ConcurrentHashMap<>();
 private ApplicationEventPublisher applicationEventPublisher;
 public CachingRouteLocator(RouteLocator delegate) {
  this.delegate = delegate;
  routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class)
    .onCacheMissResume(this::fetch);
 }
 private Flux<Route> fetch() {
  return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
 }
 @Override
 public Flux<Route> getRoutes() {
  return this.routes;
 }
 /**
  * Clears the routes cache.
  * @return routes flux
  */
 public Flux<Route> refresh() {
  this.cache.clear();
  return this.routes;
 }
 @Override
 public void onApplicationEvent(RefreshRoutesEvent event) {
  try {
   fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list)
     .materialize().collect(Collectors.toList()).subscribe(signals -> {
      applicationEventPublisher
        .publishEvent(new RefreshRoutesResultEvent(this));
      cache.put(CACHE_KEY, signals);
     }, throwable -> handleRefreshError(throwable)));
  }
  catch (Throwable e) {
   handleRefreshError(e);
  }
 }
 private void handleRefreshError(Throwable throwable) {
  if (log.isErrorEnabled()) {
   log.error("Refresh routes error !!!", throwable);
  }
  applicationEventPublisher
    .publishEvent(new RefreshRoutesResultEvent(this, throwable));
 }
 @Deprecated
 /* for testing */ void handleRefresh() {
  refresh();
 }
 @Override
 public int getOrder() {
  return 0;
 }
 @Override
 public void setApplicationEventPublisher(
   ApplicationEventPublisher applicationEventPublisher) {
  this.applicationEventPublisher = applicationEventPublisher;
 }
}

那么解决方案就自然能够想出来,只需要在服务下线时能够去实时的刷新这个缓存自然就解决了

解决方案

这里通过去监听 Nacos 实例刷新事件,一旦出现实例发生变化马上删除缓存。在删除负载均衡缓存后,Spring Cloud Gateway 在处理请求时发现没有缓存会重新拉取一遍服务列表,这样之后都是用的是最新的服务列表了,也就达到了我们动态感知上下线的目的。

代码如下:

@Component
@Slf4j
public  class NacosInstancesChangeEventListener extends Subscriber<InstancesChangeEvent> {
    @Resource
    private CacheManager defaultLoadBalancerCacheManager;
    @Override
    public void onEvent(InstancesChangeEvent event) {
        log.info("Spring Gateway 接收实例刷新事件:{}, 开始刷新缓存", JacksonUtils.toJson(event));
        Cache cache = defaultLoadBalancerCacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
        if (cache != null) {
            cache.evict(event.getServiceName());
        }
        log.info("Spring Gateway 实例刷新完成");
    }
    @Override
    public Class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
        return InstancesChangeEvent.class;
    }
}

这里通过继承的方式监听 Nacos 的 InstancesChangeEvent,在 onEvent 接收到实例刷新的信息后直接删除对应服务的负载均衡缓存,缓存的名字是定义在 Spring Gateway 的相关代码中的,直接引入即可,Cache 则是继承自 Spring Cache 接口,负载均衡缓存也继承了 Cache 接口,有了 Cache 接口就可以直接使用其接口定义的 evict 方法即可,而缓存的 key 名就则就是服务名,在 InstancesChangeEvent 中,通过 getServiceName 就可以得到服务名。

这里就不演示了,有兴趣的小伙伴可以测试一下

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
182 69
利用Spring Cloud Gateway Predicate优化微服务路由策略
|
6天前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
31 7
|
4月前
|
存储 数据可视化 Java
基于MicrometerTracing门面和Zipkin实现集成springcloud2023的服务追踪
Sleuth将会停止维护,Sleuth最新版本也只支持springboot2。作为替代可以使用MicrometerTracing在微服务中作为服务追踪的工具。
215 1
|
3月前
|
存储 Java Nacos
Spring Cloud+Nacos+KMS 动态配置最佳实践
本文讲述了 Spring Cloud 应用中结合 Nacos 实现了运行期配置动态更新的功能,以及在此基础上结合 KMS 在不改动代码的情况下对应用使用的敏感配置进行保护,解决将配置迁移到 Nacos 中可能存在的数据安全顾虑,并对其底层工作原理做了简单介绍。
712 25
|
2月前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
356 1
|
3月前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
92 3
|
4月前
|
JSON SpringCloudAlibaba Java
Springcloud Alibaba + jdk17+nacos 项目实践
本文基于 `Springcloud Alibaba + JDK17 + Nacos2.x` 介绍了一个微服务项目的搭建过程,包括项目依赖、配置文件、开发实践中的新特性(如文本块、NPE增强、模式匹配)以及常见的问题和解决方案。通过本文,读者可以了解如何高效地搭建和开发微服务项目,并解决一些常见的开发难题。项目代码已上传至 Gitee,欢迎交流学习。
275 1
Springcloud Alibaba + jdk17+nacos 项目实践
|
3月前
|
负载均衡 Java API
项目中用的网关Gateway及SpringCloud
Spring Cloud Gateway 是一个功能强大、灵活易用的API网关解决方案。通过配置路由、过滤器、熔断器和限流等功能,可以有效地管理和保护微服务。本文详细介绍了Spring Cloud Gateway的基本概念、配置方法和实际应用,希望能帮助开发者更好地理解和使用这一工具。通过合理使用Spring Cloud Gateway,可以显著提升微服务架构的健壮性和可维护性。
91 0
|
5月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
255 5
|
5月前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
140 3