写点什么

SpringCloud Gateway 路由转发性能优化

用户头像
Aaron
关注
发布于: 2021 年 06 月 13 日
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 也在其中,代码入下:

@Beanpublic RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,    RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {  return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);}
复制代码

很遗憾,官方没有给这个自动装配添加条件,我们无法自行装配替代默认装配。

我们只能采取以下步骤:

  1. 在 Springboot 启动类上增加排除 GatewayAutoConfiguration 的自动装配配置;

  2. 继承 GatewayAutoConfiguration 并完全拷贝其装配条件;

  3. 覆盖父类 routePredicateHandlerMapping 方法,给装配添加条件;

  4. 继承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  UnitsMyBenchmark.testMethod                      thrpt     20  990.298 ± 219.989  ops/sMyBenchmark.testMethod                       avgt     20    0.002 ±   0.001   s/opMyBenchmark.testMethod                     sample  20205    0.002 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.001             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.011             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.017             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.017             s/opMyBenchmark.testMethod                         ss     20    0.002 ±   0.001   s/op
复制代码

100 条路由(老版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  769.948 ± 112.572  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15364    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.008             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.015             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.015             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

100 条路由(新版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  769.099 ± 110.400  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15541    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.008             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.012             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.012             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

1K 条路由(老版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  759.265 ± 106.047  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15245    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.001             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.007             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.014             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.015             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

1K 条路由(新版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  772.978 ± 102.976  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15101    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.007             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.016             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.016             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

5K 条路由(老版本)

Benchmark                                    Mode   Cnt    Score    Error  UnitsMyBenchmark.testMethod                      thrpt    20  232.624 ±  3.330  ops/sMyBenchmark.testMethod                       avgt    20    0.008 ±  0.001   s/opMyBenchmark.testMethod                     sample  4734    0.009 ±  0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample          0.008            s/opMyBenchmark.testMethod:testMethod·p0.50    sample          0.008            s/opMyBenchmark.testMethod:testMethod·p0.90    sample          0.009            s/opMyBenchmark.testMethod:testMethod·p0.95    sample          0.009            s/opMyBenchmark.testMethod:testMethod·p0.99    sample          0.011            s/opMyBenchmark.testMethod:testMethod·p0.999   sample          0.015            s/opMyBenchmark.testMethod:testMethod·p0.9999  sample          0.016            s/opMyBenchmark.testMethod:testMethod·p1.00    sample          0.016            s/opMyBenchmark.testMethod                         ss    20    0.009 ±  0.001   s/op
复制代码

5K 条路由(新版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  783.074 ± 112.114  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15318    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.001             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.007             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.017             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.017             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

1W 条路由(老版本)

Benchmark                                    Mode   Cnt    Score    Error  UnitsMyBenchmark.testMethod                      thrpt    20  122.122 ±  1.789  ops/sMyBenchmark.testMethod                       avgt    20    0.016 ±  0.001   s/opMyBenchmark.testMethod                     sample  2464    0.016 ±  0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample          0.015            s/opMyBenchmark.testMethod:testMethod·p0.50    sample          0.016            s/opMyBenchmark.testMethod:testMethod·p0.90    sample          0.017            s/opMyBenchmark.testMethod:testMethod·p0.95    sample          0.018            s/opMyBenchmark.testMethod:testMethod·p0.99    sample          0.018            s/opMyBenchmark.testMethod:testMethod·p0.999   sample          0.029            s/opMyBenchmark.testMethod:testMethod·p0.9999  sample          0.030            s/opMyBenchmark.testMethod:testMethod·p1.00    sample          0.030            s/opMyBenchmark.testMethod                         ss    20    0.017 ±  0.001   s/op
复制代码

1W 条路由(新版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  775.200 ± 121.410  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15261    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.001             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.003             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.007             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.014             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.014             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

10W 条路由(老版本)

Benchmark                                    Mode  Cnt   Score   Error  UnitsMyBenchmark.testMethod                      thrpt   20  12.765 ± 0.338  ops/sMyBenchmark.testMethod                       avgt   20   0.159 ± 0.006   s/opMyBenchmark.testMethod                     sample  260   0.153 ± 0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample        0.147           s/opMyBenchmark.testMethod:testMethod·p0.50    sample        0.152           s/opMyBenchmark.testMethod:testMethod·p0.90    sample        0.157           s/opMyBenchmark.testMethod:testMethod·p0.95    sample        0.159           s/opMyBenchmark.testMethod:testMethod·p0.99    sample        0.163           s/opMyBenchmark.testMethod:testMethod·p0.999   sample        0.167           s/opMyBenchmark.testMethod:testMethod·p0.9999  sample        0.167           s/opMyBenchmark.testMethod:testMethod·p1.00    sample        0.167           s/opMyBenchmark.testMethod                         ss   20   0.155 ± 0.002   s/op
复制代码

10W 条路由(新版本)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  774.979 ± 115.501  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±   0.001   s/opMyBenchmark.testMethod                     sample  15422    0.003 ±   0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.50    sample           0.002             s/opMyBenchmark.testMethod:testMethod·p0.90    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.004             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.005             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.011             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.012             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码


发布于: 2021 年 06 月 13 日阅读数: 894
用户头像

Aaron

关注

老骥伏枥 2019.06.29 加入

一个立志做一辈子程序员的搬砖工人。

评论

发布
暂无评论
SpringCloud Gateway 路由转发性能优化