spring-cloud-gateway + nacos 每30s 加载路由一次

环境

spring-cloud-gateway 版本


     org.springframework.cloud
     spring-cloud-starter-gateway
      2.1.2.RELEASE

现象

目前设计的是路由存在数据库中,自定义实现了一个RouteDefinitionRepository。
打印日志发现路由每30s就加载一次。

自定义MySqlRouteDefinitionRepository 如下

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.stream.Stream;

@Slf4j
public class MySqlRouteDefinitionRepository implements RouteDefinitionRepository{


    private volatile boolean init = false;

    @Override
    public Flux getRouteDefinitions() {
        log.info("getRouteDefinitions");
        // 从数据库中加载
        Stream routeDefinitionStream = routeMapper.select().stream().map(e->routeDefinitionConverter.convert(routeDefinitionDTO));
        return Flux.fromStream(routeDefinitionStream);
    }


    @Override
    public Mono save(Mono route) {
        return Mono.empty();
    }

    @Override
    public Mono delete(Mono routeId) {
        return Mono.empty();
    }

}

由哪里触发的路由加载?

在加载路由的地方打了断点,但是由于是响应式编程,无法得知具体触发的地方。根据org.springframework.cloud.gateway.route.RouteDefinitionLocator#getRouteDefinitions 调用入口找到了
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes,然后又找到了
org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter#onApplicationEvent

具体的调用堆栈为
org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter#onApplicationEvent
org.springframework.cloud.gateway.route.CachingRouteLocator#getRoutes
org.springframework.cloud.gateway.route.CompositeRouteLocator#getRoutes
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes
org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator#getRouteDefinitions

可以看是由于收到了一个RefreshRoutesEvent事件,导致了路由的刷新。

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof PredicateArgsEvent) {
            handle((PredicateArgsEvent) event);
        }
        else if (event instanceof WeightDefinedEvent) {
            addWeightConfig(((WeightDefinedEvent) event).getWeightConfig());
        }
        else if (event instanceof RefreshRoutesEvent && routeLocator != null) {
            routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe()); // forces
                                                                                    // initialization
        }

    }

在WeightCalculatorWebFilter 打了一个断点,堆栈如下:
可以从nacosServicesWatch:130, NacosWatch (com.alibaba.cloud.nacos.discovery)这行看出是Nacos的心跳触发了路由的刷新

onApplicationEvent:133, WeightCalculatorWebFilter (org.springframework.cloud.gateway.filter)
doInvokeListener:172, SimpleApplicationEventMulticaster (org.springframework.context.event)
invokeListener:165, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:139, SimpleApplicationEventMulticaster (org.springframework.context.event)
publishEvent:402, AbstractApplicationContext (org.springframework.context.support)
publishEvent:359, AbstractApplicationContext (org.springframework.context.support)
reset:68, RouteRefreshListener (org.springframework.cloud.gateway.route)
resetIfNeeded:63, RouteRefreshListener (org.springframework.cloud.gateway.route)
onApplicationEvent:57, RouteRefreshListener (org.springframework.cloud.gateway.route)
doInvokeListener:172, SimpleApplicationEventMulticaster (org.springframework.context.event)
invokeListener:165, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:139, SimpleApplicationEventMulticaster (org.springframework.context.event)
publishEvent:402, AbstractApplicationContext (org.springframework.context.support)
publishEvent:359, AbstractApplicationContext (org.springframework.context.support)
nacosServicesWatch:130, NacosWatch (com.alibaba.cloud.nacos.discovery)
run:-1, 1684543956 (com.alibaba.cloud.nacos.discovery.NacosWatch$$Lambda$1039)
run:54, DelegatingErrorHandlingRunnable (org.springframework.scheduling.support)
call:511, Executors$RunnableAdapter (java.util.concurrent)
runAndReset$$$capture:308, FutureTask (java.util.concurrent)
runAndReset:-1, FutureTask (java.util.concurrent)

如何解决?

MySqlRouteDefinitionRepository 直接处理心跳事件
需要达到如下几点要求

  1. 第一次要从数据库中获取路由
  2. 当主动调用路由刷新接口 /refresh 时,需要从数据库中获取路由。
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteRefreshListener;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;



@Slf4j
public class MySqlRouteDefinitionRepository implements RouteDefinitionRepository, SmartApplicationListener {

    private List routes = new ArrayList<>();

    private volatile boolean init = false;

    @Override
    public Flux getRouteDefinitions() {
        log.info("getRouteDefinitions");
        return Flux.fromIterable(routes);
    }


    @Override
    public Mono save(Mono route) {
        return Mono.empty();
    }

    @Override
    public Mono delete(Mono routeId) {
        return Mono.empty();
    }

    @Override
    public boolean supportsEventType(Class eventType) {
        return RefreshRoutesEvent.class.isAssignableFrom(eventType);
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        RefreshRoutesEvent refreshRoutesEvent = (RefreshRoutesEvent) event;
        Object source = refreshRoutesEvent.getSource();
        if(source instanceof RouteRefreshListener && init){
            log.info("RouteRefreshListener return");
            return;
        }
        init = true;

        log.info("load route source:{}",source.getClass().getName());
        // 从数据库中加载
        Stream routeDefinitionStream = routeMapper.select().stream().map(e->routeDefinitionConverter.convert(routeDefinitionDTO));
        routes = routeDefinitionStream.collect(Collectors.toList());
    }
}

为什么需要心跳事件时去加载路由呢

Refresh routes on heartbeat event when using discovery client #236

你可能感兴趣的:(spring-cloud-gateway + nacos 每30s 加载路由一次)