首页 > 编程语言 >重写SpringCloudGateway路由查找算法,性能提升100倍!

重写SpringCloudGateway路由查找算法,性能提升100倍!

时间:2024-01-24 17:58:41浏览次数:32  
标签:return exchange route public 100 routeLocator SpringCloudGateway 路由

如果你也在做SpringCloudGateway网关开发,希望这篇文章能给你带来一些启发

背景

先说背景,某油项目,通过SpringCloudGateway配置了1.6万个路由规则,实际接口调用过程中,会偶现部分接口从发起请求到业务应用处理间隔了大概5秒的时间,经排查后发现是SpringCloudGateway底层在查找对应的Route时采用了遍历+断言匹配的方式,路由规则太多时就会出现耗时太久的问题,对应的源码如下:

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
	return this.routeLocator
			.getRoutes()
			//individually filter routes so that filterWhen error delaying is not a problem
			.concatMap(route -> Mono
					.just(route)
					.filterWhen(r -> {
						// add the current route we are testing
						exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
						return r.getPredicate().apply(exchange);
					})
					//instead of immediately stopping main flux due to error, log and swallow it
					.doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
					.onErrorResume(e -> Mono.empty())
			)
			// .defaultIfEmpty() put a static Route not found
			// or .switchIfEmpty()
			// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
			.next()
			//TODO: error handling
			.map(route -> {
				if (logger.isDebugEnabled()) {
					logger.debug("Route matched: " + route.getId());
				}
				validateRoute(route, exchange);
				return route;
			});

	/* TODO: trace logging
		if (logger.isTraceEnabled()) {
			logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
		}*/
}

目标

找到了问题,就需要对这块路由查找的代码进行优化,我们分析了下路由规则,发现可以用请求方法Method+请求路径Path作为key,把对应的Route作为缓存值,通过ServerWebExchange直接命中对应的路由对象(这里要注意下,如果有同样的问题,需要根据实际情况设计缓存键,比如/person/**这种断言Path就不适用了),对应的路由规则如下:

{
    "predicates": [
        {
            "args": {
                "_genkey_0": "/v1/structuredData/serviceData/cestc_dportal/MH_GX_JS_SJCZQX3080"
            },
            "name": "Path"
        },
        {
            "args": {
                "_genkey_0": "GET"
            },
            "name": "Method"
        }
    ],
    "filters": [
        {
            "args": {
                "_genkey_1": "/myapi/v1.0/zhyApi/getDataForGet",
                "_genkey_0": "/v1/structuredData/serviceData/cestc_dportal/MH_GX_JS_SJCZQX3080"
            },
            "name": "RewritePath"
        }
    ],
    "id": "02024012311262643900000101579677",
    "uri": "lb://myapi",
    "order": 0
}

定义路由缓存策略

接口定义

/**
 * 路由断言缓存实现
 * 通过ServerWebExchange快速查找Route
 * @author changxy
 */
public interface RoutePredicateCacheable {

    /**
     * 更新缓存路由
     * @param routeDefinition
     */
    void update(List<RouteDefinition> routeDefinition);

    /**
     * 根据请求上下文匹配对应路由
     * @param exchange
     * @return
     */
    Optional<Route> getRoute(ServerWebExchange exchange);

    static RoutePredicateCacheable empty() {
        return new BlankRoutePredicateCacheable();
    }

}

使用本地内存存放路由缓存

/**
 * 本地内存Route对象缓存器
 * RouteDefinitionRouteLocator类中处理RouteDefinition到Route的转换
 * @author changxy
 */
public class InMemoryRoutePredicateCacheable implements RoutePredicateCacheable {

    private final RouteDefinitionRouteLocator routeLocator;

    private final Map<String, Route> routes = new ConcurrentHashMap<>(1024);

    protected final static String CACHE_KEY_FORMAT = "%s:%s";

    public InMemoryRoutePredicateCacheable(RouteDefinitionRouteLocator routeLocator) {
        this.routeLocator = routeLocator;
    }

    @Override
    public void update(List<RouteDefinition> routeDefinitions) {
        if (CollectionUtils.isEmpty(routeDefinitions)) {
            return ;
        }

        // 清空缓存
        routes.clear();

        Map<String, Route> routeMap = this.routeLocator
                .getRoutes()
                .toStream()
                .collect(Collectors.toMap(Route::getId, r -> r));

        for (RouteDefinition routeDefinition : routeDefinitions) {
            routes.put(key(routeDefinition), routeMap.get(routeDefinition.getId()));
        }

        System.out.println(1);

    }

    @Override
    public Optional<Route> getRoute(ServerWebExchange exchange) {
        return Optional.ofNullable(routes.get(key(exchange)));
    }

    public Optional<Route> lookupRoute(String routeId) {
        return this.routeLocator
                .getRoutes()
                .toStream()
                .filter(route -> Objects.equals(route.getId(), routeId))
                .findFirst();
    }

    /**
     * 根据路由定义生成key
     * @param routeDefinition
     * @return
     */
    protected String key(RouteDefinition routeDefinition) {
        Map<String, String> routeDefinitionParams = routeDefinition.getPredicates()
                .stream()
                .collect(
                        Collectors.toMap(
                                PredicateDefinition::getName,
                                p -> p.getArgs().get("_genkey_0"),
                                (k1, k2) -> k2
                        )
                );
        if (null != routeDefinitionParams
                && routeDefinitionParams.containsKey("Method")
                && routeDefinitionParams.containsKey("Path")) {
            return String.format(CACHE_KEY_FORMAT, routeDefinitionParams.get("Method"), routeDefinitionParams.get("Path"));
        }
        return StringUtils.EMPTY;
    }

    /**
     * 根据请求对象生成key
     * @param exchange
     * @return
     */
    protected String key(ServerWebExchange exchange) {
        String method = exchange.getRequest().getMethodValue();
        String paths = exchange.getRequest().getPath().value();
        return String.format(CACHE_KEY_FORMAT, method, paths);
    }

}

我们的路由规则存放在Nacos配置中心,网关服务启动时、Nacos配置发生变更时,同步刷新路由缓存,这块可以根据实际情况定义缓存更新策略,部分伪代码如下:

List<RouteDefinition> routeDefinitions = list.stream().map(DynamicRoutingConfig.this::assembleRouteDefinition).collect(Collectors.toList());

// 20240124 更新Route缓存,优化路由匹配速度
routePredicateCacheable.update(routeDefinitions);

重写RoutePredicateHandlerMapping

public class CachingRoutePredicateHandlerMapping extends RoutePredicateHandlerMapping {

    private final RoutePredicateCacheable routePredicateCacheable;

    public CachingRoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment, RoutePredicateCacheable routePredicateCacheable) {
        super(webHandler, routeLocator, globalCorsProperties, environment);
        this.routePredicateCacheable = routePredicateCacheable;
    }

    @Override
    protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
        Optional<Route> route = routePredicateCacheable.getRoute(exchange);
        if (route.isPresent()) {
            return Mono.just(route.get());
        } else {
            return super.lookupRoute(exchange);
        }
    }

}

定义AutoConfiguration

@Configuration
@ConditionalOnProperty(name = "route.cache.enabled", matchIfMissing = false)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
public class FastRoutePredicateHandlerAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public RoutePredicateCacheable routePredicateCacheable(RouteDefinitionRouteLocator routeLocator) {
        return new InMemoryRoutePredicateCacheable(routeLocator);
    }

    @Bean("cachingRoutePredicateHandlerMapping")
    public RoutePredicateHandlerMapping routePredicateHandlerMapping(
            FilteringWebHandler webHandler, RouteLocator routeLocator,
            GlobalCorsProperties globalCorsProperties, Environment environment, RoutePredicateCacheable routePredicateCacheable) {
        return new CachingRoutePredicateHandlerMapping(webHandler, routeLocator,
                globalCorsProperties, environment, routePredicateCacheable);
    }

}

不加载SpringCloudGateway自己的RoutePredicateHandlerMapping

@Configuration
@ConditionalOnProperty(name = "route.cache.enabled", matchIfMissing = false)
public class RoutePredicateBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        registry.removeBeanDefinition("routePredicateHandlerMapping");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

优化效果测试

毫秒级响应了

标签:return,exchange,route,public,100,routeLocator,SpringCloudGateway,路由
From: https://www.cnblogs.com/changxy-codest/p/17985365

相关文章

  • thinkphp 路由参数 域名 miss设置
    路由的使用在route/app.php文件里设置路由usethink\facade\Route;//rule()//还有其他的快捷方式Route::GETPOSTPUTDELETEPATCHHEADOPTIONSANY//Route::rule('index/:id','Login/index','GET|POST')->https();//Route::get('index/:......
  • TPLINK路由器重启脚本(软件版本3.0.0)
    ​ 家中的两个路由器全都是TPLink路由器,由于总出现时间一长就网卡的原因,写了这个重启脚本在每天凌晨五点的时候对路由器进行自动重启使用方法:​ self.logindata的值为登录时的json数据​ 打开F12控制台,复制登陆的json数据包并填入self.logindataimportrequestsfromdatet......
  • 华为二层交换机与路由器对接上网配置实验
    二层交换机与路由器对接上网配置示例组网图形图1二层交换机与路由器对接上网组网图二层交换机简介配置注意事项组网需求配置思路操作步骤配置文件相关信息二层交换机简介二层交换机指的是仅能够进行二层转发,不能进行三层转发的交换机。也就是说仅支持二层特性,不支持......
  • 华为三层交换机与路由器配置上网示例——学会这个,你就是IT界大佬
    特性配置案例适用的产品和版本说明本手册适用于通过命令行配置的框式交换机和盒式交换机(S300、S500、S2700、S3700、S5700、S6700、S7700、S7900、S9700共用一套)的多个版本,每个案例所支持的产品和版本不同,每个案例适用产品和版本请参看具体页面中的“配置注意事项”。若无特殊说明,......
  • Mygin实现分组路由Group
    本篇是Mygin第五篇目的实现路由分组为什么要分组分组控制(GroupControl)是Web框架应该提供的基础功能之一,对同一模块功能的开发,应该有相同的前缀。或者对一部分第三方接口,统一需要加解密等功能。分组后很方便。例如:对于任务模块,统一前缀为/task除去/user/login接口,都......
  • 退役前要做的 100 件事
    给自己起个ID\(✓\)@Creeper_l@YellowRose爆零一场模拟赛\(✓\)2023/07/31AK一场模拟赛\(✓\)2023/8/12记下第一次提交的日期\(✓\)2020-12-0720:19:43向大佬请教问题\(✓\)@World_Ender对自己的板子越看越满意\(✓\)有源汇上下界最大流......
  • 路由策略(前缀列表,策略工具-filter-policy,策略工具-Router-policy,双点双向路由重发布)
    1.前缀列表默认是拒绝,如果没写允许,就都是拒绝Greater-equal26less-equal32从子网掩码26-32被匹配,其他的被拒绝2.策略工具1:filter-policy(过滤策略)Export只对引入的路由,,对引入的路由在过滤,是不是发给我的邻居使用,import对所有路由器都可用*ospf:import*R1传......
  • router4j--SpringCloud动态路由利器
    前言本文介绍Java的动态路由中间件:router4j。router4j用于SpringCloud项目,它可以将某个url请求路由到指定的机器上,也可以将所有请求强制转到指定机器。问题描述Java后端在开发SpringCloud项目时如果同一个应用起了多个实例,会遇到以下问题:无法将指定url请求强制转到个人电脑。这样会......
  • 初中英语优秀范文100篇-067I'm Proud of Myself-我为自己感到骄傲
    PDF格式公众号回复关键字:SHCZFW067记忆树1I'mproudofmyselfbecauseofawonderfulexperience.翻译我很骄傲,因为我有一段美好的经历。简化记忆骄傲句子结构"I"是主语,表示句子的行动者。"amproudof"是谓语,表示主语的状态或动作。"myself"是宾语,表示动作......
  • Discarded invalid param(s) "xxx" when navigating.路由params传值报错
    从VueRouter的2022-8-22更新后,无法使用name+params的方式进行传递,官方给出的说法是所以我采用了HistoryAPI的模式A组件import{useRouter}from"vue-router"letrouter=useRouter()constparams={title:'标题'}functiontoRouter(){router.push({name:......