spring-cloud-gateway 处理请求的时候触发路由加载

环境

spring-cloud-gateway 版本


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

现象

监控发现某些请求响应时间特别长,查看链路追踪,发现请求触发了路由加载,由于使用自定义MySqlRouteDefinitionRepository从数据库里获取路由,导致了数据库负载特别高。

请求查找路由堆栈

org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute
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

由请求堆栈可以看到中间使用CachingRouteLocator对路由进行了缓存

CachingRouteLocator 源码

spring-cloud-gateway 2.1.2.RELEASE 版本

当收到RefreshRoutesEvent事件时,会调用refresh()方法清除缓存,导致了请求的时候也可能遇上缓存为空而导致触发加载路由。

由于当前项目使用了nacos,当发生心跳的时候会触发 RefreshRoutesEvent 导致清除缓存,刷新路由。细节可查看我之前的文章spring-cloud-gateway+nacos 每30s 加载路由一次

此时缓存清空了,刚好收到请求也会触发路由加载。

public class CachingRouteLocator
        implements RouteLocator, ApplicationListener {

    private final RouteLocator delegate;

    private final Flux routes;

    private final Map cache = new HashMap<>();

    public CachingRouteLocator(RouteLocator delegate) {
        this.delegate = delegate;
        routes = CacheFlux.lookup(cache, "routes", Route.class)
                .onCacheMissResume(() -> this.delegate.getRoutes()
                        .sort(AnnotationAwareOrderComparator.INSTANCE));
    }

    @Override
    public Flux getRoutes() {
        return this.routes;
    }

    /**
     * Clears the routes cache.
     * @return routes flux
     */
    public Flux refresh() {
        this.cache.clear();
        return this.routes;
    }

    @Override
    public void onApplicationEvent(RefreshRoutesEvent event) {
        refresh();
    }

    @Deprecated
    /* for testing */ void handleRefresh() {
        refresh();
    }

}

spring-cloud-gateway 4.0.7版本

可以看到收到RefreshRoutesEvent事件时,没有清除缓存,那么路由会直接从缓存中获取,不会触发路由加载。

package org.springframework.cloud.gateway.route;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.cache.CacheFlux;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.event.RefreshRoutesResultEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;

/**
 * @author Spencer Gibb
 */
public class CachingRouteLocator
        implements Ordered, RouteLocator, ApplicationListener, ApplicationEventPublisherAware {

    private static final Log log = LogFactory.getLog(CachingRouteLocator.class);

    private static final String CACHE_KEY = "routes";

    private final RouteLocator delegate;

    private final Flux routes;

    private final Map 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 fetch() {
        return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
    }

    private Flux fetch(Map metadata) {
        return this.delegate.getRoutesByMetadata(metadata).sort(AnnotationAwareOrderComparator.INSTANCE);
    }

    @Override
    public Flux getRoutes() {
        return this.routes;
    }

    /**
     * Clears the routes cache.
     * @return routes flux
     */
    public Flux refresh() {
        this.cache.remove(CACHE_KEY);
        return this.routes;
    }

    @Override
    public void onApplicationEvent(RefreshRoutesEvent event) {
        try {
            if (this.cache.containsKey(CACHE_KEY) && event.isScoped()) {
                final Mono> scopedRoutes = fetch(event.getMetadata()).collect(Collectors.toList())
                        .onErrorResume(s -> Mono.just(List.of()));

                scopedRoutes.subscribe(scopedRoutesList -> {
                    Flux.concat(Flux.fromIterable(scopedRoutesList), getNonScopedRoutes(event)).materialize()
                            .collect(Collectors.toList()).subscribe(signals -> {
                                applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
                                cache.put(CACHE_KEY, signals);
                            }, this::handleRefreshError);
                }, this::handleRefreshError);
            }
            else {
                final Mono> allRoutes = fetch().collect(Collectors.toList());

                allRoutes.subscribe(list -> Flux.fromIterable(list).materialize().collect(Collectors.toList())
                        .subscribe(signals -> {
                            applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
                            cache.put(CACHE_KEY, signals);
                        }, this::handleRefreshError), this::handleRefreshError);
            }
        }
        catch (Throwable e) {
            handleRefreshError(e);
        }
    }

    private Flux getNonScopedRoutes(RefreshRoutesEvent scopedEvent) {
        return this.getRoutes()
                .filter(route -> !RouteLocator.matchMetadata(route.getMetadata(), scopedEvent.getMetadata()));
    }

    private void handleRefreshError(Throwable throwable) {
        if (log.isErrorEnabled()) {
            log.error("Refresh routes error !!!", throwable);
        }
        applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this, throwable));
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

}

参考资料

csdn Spring Cloud Gateway 之踩坑日记

你可能感兴趣的:(spring-cloud-gateway 处理请求的时候触发路由加载)