如果你也在做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 { }
}

优化效果测试

毫秒级响应了

重写SpringCloudGateway路由查找算法,性能提升100倍!的更多相关文章

  1. 优化临时表使用,SQL语句性能提升100倍

    [问题现象] 线上mysql数据库爆出一个慢查询,DBA观察发现,查询时服务器IO飙升,IO占用率达到100%, 执行时间长达7s左右.SQL语句如下:SELECT DISTINCT g.*, cp. ...

  2. 转--优化临时表使用,SQL语句性能提升100倍

    转自:http://www.51testing.com/html/01/n-867201-2.html [问题现象] 线上mysql数据库爆出一个慢查询,DBA观察发现,查询时服务器IO飙升,IO占用 ...

  3. Web 应用性能提升 10 倍的 10 个建议

    转载自http://blog.jobbole.com/94962/ 提升 Web 应用的性能变得越来越重要.线上经济活动的份额持续增长,当前发达世界中 5 % 的经济发生在互联网上(查看下面资源的统计 ...

  4. MySQL 5.7 优化SQL提升100倍执行效率的深度思考(GO)

    系统环境:微软云Linux DS12系列.Centos6.5 .MySQL 5.7.10.生产环境,step1,step2是案例,精彩的剖析部分在step3,step4. 1.慢sql语句大概需要13 ...

  5. 重构、插件化、性能提升 20 倍,Apache DolphinScheduler 2.0 alpha 发布亮点太多!

    点击上方 蓝字关注我们 社区的小伙伴们,好消息!经过 100 多位社区贡献者近 10 个月的共同努力,我们很高兴地宣布 Apache DolphinScheduler 2.0 alpha 发布.这是 ...

  6. 阿里云maven仓库地址,速度提升100倍

    参照:https://www.cnblogs.com/xxt19970908/p/6685777.html maven仓库用过的人都知道,国内有多么的悲催.还好有比较好用的镜像可以使用,尽快记录下来. ...

  7. 性能提升 40 倍!我们用 Rust 重写了自己的项目

    前言 Rust 已经悄然成为了最受欢迎的编程语言之一.作为一门新兴底层系统语言,Rust 拥有着内存安全性机制.接近于 C/C++ 语言的性能优势.出色的开发者社区和体验出色的文档.工具链和IDE 等 ...

  8. 如何利用缓存机制实现JAVA类反射性能提升30倍

    一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...

  9. 查询性能提升3倍!Apache Hudi 查询优化了解下?

    从 Hudi 0.10.0版本开始,我们很高兴推出在数据库领域中称为 Z-Order 和 Hilbert 空间填充曲线的高级数据布局优化技术的支持. 1. 背景 Amazon EMR 团队最近发表了一 ...

  10. Elasticsearch Reindex性能提升10倍+实战

    文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484134&idx=1&sn=750249a ...

随机推荐

  1. 如何使用Redisson实现分布式锁?

    在分布式系统中,当多个线程(或进程)同时操作同一个资源时,为了保证数据一致性问题,所以就需要一种机制来确保在同一时间只有一个线程(或进程)能够对资源进行修改,这就是分布式锁的作用. 分布式锁是一种在分 ...

  2. Redis的五大数据类型(简单使用)

    1:Redis的简单介绍? Redis(Remote Dictionary Server ):即远程字典服务 是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型Key-Val ...

  3. kubernetes之部署war项目(二)

    kubernetes之部署war项目(二) k8s系列 源自我工作上的实际场景,记录于此. 现在老项目是war包形式,需要基于tomcat部署,因此在打包镜像时将tomcat带上的. 假设我的war项 ...

  4. Golang实现JAVA虚拟机-指令集和解释器

    原文链接:https://gaoyubo.cn/blogs/f57f32cf.html 前置 Golang实现JAVA虚拟机-解析class文件 Golang实现JAVA虚拟机-运行时数据区 一.字节 ...

  5. 华为云GaussDB为MetaERP“成本核算”产品“保驾护航”

    摘要:华为宣布实现了自主创新的MetaERP研发,并且完成了对旧ERP系统的全面替换,这其中,就采用了华为云GaussDB数据库特有的全密态技术,对ERP系统中的绝密数据进行加密保护,从而保障了数据的 ...

  6. 高性能利器!华为云MRS ClickHouse重磅推出!

    摘要:华为智能数据湖MRS服务即将上线ClickHouse高性能引擎集群,用户只需要几分钟,就可以轻松方便地一键式完成集群部署搭建,快速拥有PB级数据的秒级交互查询分析能力,帮助用户带来极致的性能体验 ...

  7. 你眼中的程序员 VS 程序员眼中的自己,是时候打破代沟了

    摘要:修电脑?格子衫?脱发?程序员被误解了怎么办?如何一句话向父母说明白你的工作? 有人说,你们程序员工作赚钱真简单,电脑上按按键盘就行了,一点也不辛苦. 有人说,程序员不懂生活,就知道天天对着电脑. ...

  8. 性能提升1400+倍,快来看MySQL Volcano模型迭代器的谓词位置优化详解

    摘要:性能提升1400+倍,快来看MySQL Volcano模型迭代器的谓词位置优化详解. 本文分享自华为云社区<华为云数据库内核专家为您揭秘MySQL Volcano模型迭代器性能提升千倍的秘 ...

  9. 学习 Java 还是 Solon 简单,v1.9.0 发布

    入手很简单哦: pom.xml 添加依赖 <dependency> <groupId>org.noear</groupId> <artifactId>s ...

  10. SQL Server 时间算差值/常用函数

    项目中需要计算使用年限,按月份算.刚开始踩了坑,不足1年应该按1年算.记录下~ 和当前时间比较,用DATEDIFF函数DateDiff(month,比较的时间,getdate())先算出月份,再除以1 ...