SpringCloud Gateway 路由转发性能优化
接上篇,通过测试验证,发现随着路由增长,路由性能会严重下降。
本篇,针对采用 Path 方式路由的进行性能优化,注意该【优化】仅适用于特定场景,不具备普适性。
查阅源码
通过阅读
RoutePredicateHandlerMapping.java
这个类是 SpringCloud Gateway 接收 Web 请求,并查找匹配路由,具体方法为:
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()); }
*/
}
如果对源码简单做一下修改,比如,Path 匹配 /mock/**
则对路由查找结果进行缓存(注意这里缓存策略和方式仅仅是举例,根据实际需求情况来做)
public static final String MOCK_PATCH = "/mock/**";
private Map<String, Route> hashCache = new ConcurrentHashMap<>(1024);
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
String path = exchange.getRequest().getPath().subPath(0).value();
//符合Path规则,优先从缓存Map获取,时间复杂度近似于O(1)
if (pathMatcher.match(MOCK_PATCH, path)) {
return Mono.justOrEmpty(hashCache.get(path))
.switchIfEmpty(getRouteMono(exchange, path));
}
return getRouteMono(exchange, path);
}
private Mono<Route> getRouteMono(ServerWebExchange exchange, String path) {
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);
//符合Path规则,缓存路由
if (pathMatcher.match(MOCK_PATCH, path)) {
hashCache.put(path, route);
}
return route;
});
}
继续翻阅源码,找到RoutePredicateHandlerMapping 是如何装配的。
在GatewayAutoConfiguration 中实现了 SpringCloud Gateway 内部组件的自动装配,RoutePredicateHandlerMapping 也在其中,代码入下:
@Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}
很遗憾,官方没有给这个自动装配添加条件,我们无法自行装配替代默认装配。
我们只能采取以下步骤:
在 Springboot 启动类上增加排除 GatewayAutoConfiguration 的自动装配配置;
继承 GatewayAutoConfiguration 并完全拷贝其装配条件;
覆盖父类 routePredicateHandlerMapping 方法,给装配添加条件;
继承RoutePredicateHandlerMapping ,覆盖其 lookupRoute 方法,符合一定条件的请求,优先从缓存中查找路由。
改造 Gateway
修改启动类
@SpringBootApplication(exclude = GatewayConfiguration.class)
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
继承 GatewayAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class})
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class CustomGatewayAutoConfiguration extends GatewayAutoConfiguration {
// 实现自定义的RoutePredicateHandlerMapping装配
@Bean
public CustomRoutePredicateHandlerMapping customRoutePredicateHandlerMapping(
// 通过@Qualifier 制定装配的缓存管理器
@Qualifier("routeCacheManager")
CacheManager routeCacheManager,
FilteringWebHandler webHandler, RouteLocator routeLocator,
GlobalCorsProperties globalCorsProperties, Environment environment) {
return new CustomRoutePredicateHandlerMapping(
cacheManager, webHandler, routeLocator, globalCorsProperties, environment);
}
// 覆盖父类同名方法,增加使之失效的条件
@Bean
@ConditionalOnMissingBean(RoutePredicateHandlerMapping.class)
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties,
Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties,
environment);
}
}
继承 RoutePredicateHandlerMapping
public class CustomRoutePredicateHandlerMapping extends RoutePredicateHandlerMapping {
private final Cache specialCache;
public CustomRoutePredicateHandlerMapping(
CacheManager cacheManager,
FilteringWebHandler webHandler,
RouteLocator routeLocator,
GlobalCorsProperties globalCorsProperties,
Environment environment) {
super(webHandler, routeLocator, globalCorsProperties, environment);
specialCache = cacheManager.getCache("specialRouteCache");
}
@Override
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
//1. 从exchange中获取请求特征,如path
//2. 如果符合特征 则使用缓存,从缓存获取,如果缓存未命中,
// 调用 super.lookupRoute(exchange) 并加入缓存
//3. 不符合特征的,直接调用
// 下面演示使用 caffeine 缓存的方式
String specialPath = exchange.getRequest().getPath().subPath(0).value();
// 判断path是否符合缓存规则(一般而言用于仅采用Path断言,或简单结合header或query的情况,下面以只有path为例)
if (checkPath(specialPath)) {
return CacheMono
// 查找缓存
.lookup(
key -> Mono.justOrEmpty(specialCache.get(key, Route.class)).map(Signal::next),
toKey(specialPath))
// 未命中直接查找路由表
.onCacheMissResume(
() -> super.lookupRoute(exchange))
// 然后写到缓存
.andWriteWith(
(key, signal) -> Mono.fromRunnable(
() -> Optional.ofNullable(signal.get())
.ifPresent(value -> specialCache.put(key, value))
));
}
return super.lookupRoute(exchange);
}
/**
* 校验请求特征的方法,此处仅是举例
*/
private boolean checkPath(String path) {
return true;
}
/**
* 生成cacheKey的方式,此处仅是举例
*/
private String toKey(String specialPath) {
return specialPath;
}
}
缓存管理配置
@Configuration
@AutoConfigureBefore(CustomGatewayAutoConfiguration.class)
public class CacheManagerConfiguration {
@Bean
@Primary
public CacheManager defaultCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
CaffeineSpec spec = CaffeineSpec
.parse("initialCapacity=64,maximumSize=512,expireAfterWrite=300s");
cacheManager.setCacheNames(null);
return cacheManager;
}
@Bean
public CacheManager routeCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
CaffeineSpec spec = CaffeineSpec
.parse("initialCapacity=512,maximumSize=2048,expireAfterWrite=3000s");
cacheManager.setCacheNames(null);
return cacheManager;
}
}
以上就简单实现了对 Gateway 的改造,结合业务场景进行具体的性能优化即可,优化后,在路由表较大时(大于 5000 条)能较为明显的提升网关路由性能。
备用改造方案(不推荐该方式)
查阅GatewayAutoConfiguration 源码,类定义头部有一个装配条件:
@Configuration(proxyBeanMethods = false)
// 该条件可以作为装配点
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayReactiveLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration
可以在配置文件中配置
spring.cloud.gateway.enabled=false
然后将GatewayAutoConfiguration 拷贝到我们自己的工程中,去掉装配条件
// @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
同样的,我们需要找到所有依赖此条件装配的类,进行上述操作
至此修改完成,可以进行下一步测试验证。
测试结果
通过以上图表对比,可以发现,改造后,路由转发性能与路由表大小没有直接关联关系了,性能得到了较大提升。
源码下载
https://gitee.com/eblog/scgw-benchmark-all
https://gitee.com/eblog/scg-dynamic-route
测试记录
直连对照组
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 990.298 ± 219.989 ops/s
MyBenchmark.testMethod avgt 20 0.002 ± 0.001 s/op
MyBenchmark.testMethod sample 20205 0.002 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.011 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.017 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.017 s/op
MyBenchmark.testMethod ss 20 0.002 ± 0.001 s/op
100 条路由(老版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 769.948 ± 112.572 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15364 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.008 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.015 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.015 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
100 条路由(新版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 769.099 ± 110.400 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15541 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.008 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.012 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.012 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
1K 条路由(老版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 759.265 ± 106.047 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15245 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.007 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.014 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.015 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
1K 条路由(新版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 772.978 ± 102.976 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15101 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.007 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.016 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.016 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
5K 条路由(老版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 232.624 ± 3.330 ops/s
MyBenchmark.testMethod avgt 20 0.008 ± 0.001 s/op
MyBenchmark.testMethod sample 4734 0.009 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.008 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.008 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.009 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.009 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.011 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.015 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.016 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.016 s/op
MyBenchmark.testMethod ss 20 0.009 ± 0.001 s/op
5K 条路由(新版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 783.074 ± 112.114 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15318 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.007 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.017 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.017 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
1W 条路由(老版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 122.122 ± 1.789 ops/s
MyBenchmark.testMethod avgt 20 0.016 ± 0.001 s/op
MyBenchmark.testMethod sample 2464 0.016 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.015 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.016 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.017 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.018 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.018 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.029 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.030 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.030 s/op
MyBenchmark.testMethod ss 20 0.017 ± 0.001 s/op
1W 条路由(新版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 775.200 ± 121.410 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15261 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.003 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.007 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.014 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.014 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
10W 条路由(老版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 12.765 ± 0.338 ops/s
MyBenchmark.testMethod avgt 20 0.159 ± 0.006 s/op
MyBenchmark.testMethod sample 260 0.153 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.147 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.152 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.157 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.159 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.163 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.167 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.167 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.167 s/op
MyBenchmark.testMethod ss 20 0.155 ± 0.002 s/op
10W 条路由(新版本)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 774.979 ± 115.501 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 15422 0.003 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.005 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.011 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.012 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
版权声明: 本文为 InfoQ 作者【Aaron】的原创文章。
原文链接:【http://xie.infoq.cn/article/bebacc42bad0712638ba3231e】。文章转载请联系作者。
Aaron
老骥伏枥 2019.06.29 加入
一个立志做一辈子程序员的搬砖工人。
评论