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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月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)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
23天前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
56 3
|
15天前
|
负载均衡 监控 Java
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
|
1天前
|
Java API 微服务
服务网关Gateway
该博客文章详细介绍了Spring Cloud Gateway的使用方法和概念。文章首先阐述了API网关在微服务架构中的重要性,解释了客户端直接与微服务通信可能带来的问题。接着,文章通过具体的示例代码,展示了如何在Spring Cloud Gateway中添加依赖、编写路由规则,并对路由规则中的基本概念如Route、Predicate和Filter进行了详细解释。最后,文章还提供了路由规则的测试方法。
服务网关Gateway
|
4天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
23天前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
148 15
|
22天前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
41 3
|
22天前
|
Java 微服务 Spring
SpringCloud gateway自定义请求的 httpClient
SpringCloud gateway自定义请求的 httpClient
46 3
|
5天前
|
Dubbo Java Nacos
【实战攻略】破解Dubbo+Nacos+Spring Boot 3 Native打包后运行异常的终极秘籍——从零开始彻底攻克那些让你头疼不已的技术难题!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但使用Dubbo+Nacos+Spring Boot 3进行GraalVM native打包后常遇运行异常。本文剖析此问题及其解决策略:确认GraalVM版本兼容性;配置反射列表以支持必要类和方法;采用静态代理替代动态代理;检查并调整配置文件;禁用不支持的功能;利用日志和GraalVM诊断工具定位问题;根据诊断结果调整GraalVM配置。通过系统排查方法,能有效解决此类问题,确保服务稳定运行。
19 0
|
14天前
|
负载均衡 Java 应用服务中间件
Gateway服务网关
本节针对微服务中另一重要组件:网关 进行了实战性演练,网关作为分布式架构中的重要中间件,不仅承担着路由分发(重点关注Path规则配置),同时可根据自身负载均衡策略,对多个注册服务实例进行均衡调用。本节我们借助GateWay实现的网关只是技术实现的方案之一,后续大家可能会接触像:Zuul、Kong等,其实现细节或有差异,但整体目标是一致的。
|
27天前
|
消息中间件 Java Nacos
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
48 0