写点什么

SpringCloud Gateway 路由数量对性能的影响研究

用户头像
Aaron
关注
发布于: 2021 年 06 月 13 日
SpringCloud Gateway 路由数量对性能的影响研究

背景描述

近期在公司建设 API 治理平台过程中,以 SpringCloud Gateway 为基础,构建了一个 API 的 Mock 服务,以 API 的 URI 作为路由,根据服务端存储的 API DSL,验证请求信息,生成并返回 Mock 报文。


SpringCloud Gateway 具备很好的 动态路由 支持功能,可以在 API DSL 创建的同时,创建一条 Mock 路由,这样 API DSL 创建后,开发人员就可以使用 Mock 服务进行开发调试工作。


但作为一个企业级应用,所管辖的 API 的数量众多,SpringCloud Gateway 在路由表急剧膨胀后的性能如何?目前没有查阅到明确说明的资料。翻阅其源码发现,断言命中其实是进行的遍历,这样路由表膨胀时,路由性能存疑,需要进行验证,根据验证结果制定近一步优化方案。

验证方法

保持其他所有变量不变,仅调整路由表大小,使用 JMH 工具进行基准测试,得出路由表膨胀与路由性能的关系。

需要编写三个服务:

  1. 路由网关

  2. 挡板服务(路由转发的 upstream)

  3. JMH 测试端


配置信息

  CPU Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz
基准速度: 3.60 GHz 插槽: 1 内核: 8 逻辑处理器: 8 虚拟化: 已启用 L1 缓存: 512 KB L2 缓存: 2.0 MB L3 缓存: 12.0 MB
测试期间CPU利用率约 50%
SpringCloud Gateway 3.0.3, Java 1.8.0_251
复制代码


测试过程需控制变量,所以 CPU 负债不能过高,下图是我执行测试时的 CPU 负载情况

路由定义示例


{    "_id": {        "$oid": "60c4f055588bca06132e44cb"    },    "predicates": [{        "name": "Path",        "args": {            "_genkey_0": "/1999"        }    }],    "filters": [{        "name": "AddRequestHeader",        "args": {            "_genkey_0": "x-benchmark-routeId",            "_genkey_1": "60c4f055588bca06132e44cb"        }    }, {        "name": "PrefixPath",        "args": {            "_genkey_0": "/ok"        }    }],    "uri": "http://localhost:9999",    "metadata": {},    "order": 1,    "_class": "com.example.scgw.benchmark.route.MongoRouteDefinition"}
复制代码

测试结论



见上图,测试结果无论系统的吞吐量,还是响应时间指标,都随着路由表的膨胀变差,当路由表膨胀到 10W 级别时,服务基本不可用了!


这一结论印证了我的猜想。

优化思路一

保持路由网关的通用性(SpringCloud Gateway 设计了很多路由断言手段,包括基于 path、method、header、parameter 等等),采用两级(多级)路由机制见下图:



优化思路二

Mock 服务是一个专有场景,仅根据 Path 和指定 Header 进行转发,可以修改 SpringCloud Gateway 源码,提供一个根据 Path+指定 Header 查找路由的 HashMap,这样进行路由断言时就不需进行路由表遍历。


个人倾向于按照思路二进行优化,优化过程见SpringCloud Gateway 路由转发性能优化

测试相关代码

路由网关

pom.xml


<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId></dependency><dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-gateway</artifactId></dependency>
复制代码


MongoRouteDefinition.java


@Documentpublic class MongoRouteDefinition extends RouteDefinition {
@Id private String id;
@Override public String getId() { return this.id; }
@Override public void setId(String id) { this.id = id; }
public static MongoRouteDefinition from(RouteDefinition route) { MongoRouteDefinition newRoute = new MongoRouteDefinition(); BeanUtils.copyProperties(route, newRoute); return newRoute; }}
复制代码


MongoRouteRepository.java


public interface MongoRouteRepository extends    ReactiveMongoRepository<MongoRouteDefinition, String> {
}
复制代码


MongoRouteDefinitionRepository.java


@Componentpublic class MongoRouteDefinitionRepository implements RouteDefinitionRepository {
private final MongoRouteRepository mongoRouteRepository;
public MongoRouteDefinitionRepository( MongoRouteRepository mongoRouteRepository) { this.mongoRouteRepository = mongoRouteRepository; }
@Override public Flux<RouteDefinition> getRouteDefinitions() { return mongoRouteRepository.findAll().map(r -> r); }
@Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.flatMap( r -> mongoRouteRepository.save(MongoRouteDefinition.from(r)) .and(Mono.empty()) ); }
@Override public Mono<Void> delete(Mono<String> routeId) { return routeId.flatMap(mongoRouteRepository::deleteById); }
public Mono<MongoRouteDefinition> save(MongoRouteDefinition route) { return mongoRouteRepository.save(route); } public Mono<Void> delete(String routeId) { return mongoRouteRepository.deleteById(routeId); }
public Flux<MongoRouteDefinition> saveAll(Flux<RouteDefinition> routes) { return mongoRouteRepository .saveAll(routes.map(MongoRouteDefinition::from)); }
public Flux<MongoRouteDefinition> saveAll(List<MongoRouteDefinition> routes) { return mongoRouteRepository.saveAll(routes); }
public Mono<Void> deleteAll() { return mongoRouteRepository.deleteAll(); }}
复制代码


MongoRouteDefinitionRepositoryTest.java


@SpringBootTestclass MongoRouteDefinitionRepositoryTest {
public static final String UP_STREAM = "http://localhost:9999"; @Autowired MongoRouteDefinitionRepository mongoRepository;
@Test void save() { Mono<MongoRouteDefinition> route = mongoRepository.save(newRoute(-1));
StepVerifier.create(route) .expectNextMatches(r -> r.getId() != null) .expectComplete() .verify(); }
@Test void saveAll() { this.deleteAll(); int cycle = 100; while (cycle-- > 0) { int count = 100; List<MongoRouteDefinition> routes = new ArrayList<>(count); while (count-- > 0) { routes.add(newRoute(cycle * 100 + count)); } mongoRepository.saveAll(routes).blockLast(); } }
@Test void deleteAll() { mongoRepository.deleteAll().block(); }
private MongoRouteDefinition newRoute(int path) { MongoRouteDefinition route = new MongoRouteDefinition(); route.setId(ObjectId.get().toHexString()); PredicateDefinition predicate = new PredicateDefinition( "Path=/mock/" + path); route.setPredicates(Collections.singletonList(predicate)); List<FilterDefinition> filters = new LinkedList<>(); filters.add(new FilterDefinition("AddRequestHeader=x-benchmark-routeId," + route.getId())); filters.add(new FilterDefinition("PrefixPath=/ok")); route.setFilters(filters); route.setOrder(1); route.setUri(URI.create(UP_STREAM)); return route; }
复制代码


application.properties


server.port=9999
复制代码

挡板服务

pom.xml


<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-webflux</artifactId></dependency>
复制代码


java


public class MockApplication {
public static void main(String[] args) { SpringApplication.run(MockApplication.class, args); } @Bean RouterFunction<ServerResponse> defaultRouter() { return route(path("/ok/**"), this::success); }
Mono<ServerResponse> success(ServerRequest request) { String routeId = request.headers().firstHeader("x-benchmark-routeId"); return ok() .header("x-mock-server-routeId", routeId) .contentType(MediaType.TEXT_PLAIN) .body(Mono.just(String.valueOf(routeId)), String.class); }}
复制代码


application.properties


server.port=9999
复制代码

JMH 代码

创建工程


mvn archetype:generate \          -DinteractiveMode=false \          -DarchetypeGroupId=org.openjdk.jmh \          -DarchetypeArtifactId=jmh-java-benchmark-archetype \          -DgroupId=com.example \          -DartifactId=jmh \          -Dversion=1.32
复制代码


修改 POM 文件,添加 http 客户端依赖


<dependency>  <groupId>com.squareup.okhttp3</groupId>  <artifactId>okhttp</artifactId>  <version>4.9.1</version></dependency>
复制代码


编写测试案例


public class MyBenchmark {
@Benchmark @Threads(2) @Fork(2) @BenchmarkMode(Mode.All) @Warmup(iterations = 1, time = 3) @Measurement(iterations = 10, time = 1) @Timeout(time = 300) public void testMethod() { // 根据路由总数设置随机路由path int path = (int) (Math.random() * 100000); testRoute(path); }
/** * 待测试方法 */ public void testRoute(int path) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://127.0.0.1:8888/mock/" + path) .build(); Call call = client.newCall(request); try { Response response = call.execute(); assert response.body() != null; } catch (Exception e) { e.printStackTrace(); } }}
复制代码


执行性能基准测试


方式 1,IDEA 的 JMH 插件 JMH plugin 安装后,直接在 IDEA 里运行;


方式 2,mvn 打包,执行 jar 包


mvn packagejava -jar target/benchmarks.jar
复制代码


收集测试结果


测试结果数据见下文,共 6 组。

测试数据

对照组(直连)

Benchmark                                    Mode    Cnt    Score     Error  UnitsMyBenchmark.testMethod                      thrpt     20  899.152 ± 137.062  ops/sMyBenchmark.testMethod                       avgt     20    0.002 ±   0.001   s/opMyBenchmark.testMethod                     sample  17647    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.003             s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.012             s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.018             s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.018             s/opMyBenchmark.testMethod                         ss     20    0.003 ±   0.001   s/op
复制代码

100 条路由

Benchmark                                    Mode    Cnt    Score    Error  UnitsMyBenchmark.testMethod                      thrpt     20  713.931 ± 97.302  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±  0.001   s/opMyBenchmark.testMethod                     sample  14090    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.004            s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004            s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.005            s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.008            s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.011            s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.011            s/opMyBenchmark.testMethod                         ss     20    0.003 ±  0.001   s/op
复制代码

1000 条路由

Benchmark                                    Mode    Cnt    Score    Error  UnitsMyBenchmark.testMethod                      thrpt     20  630.823 ± 41.301  ops/sMyBenchmark.testMethod                       avgt     20    0.003 ±  0.001   s/opMyBenchmark.testMethod                     sample  12617    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.004            s/opMyBenchmark.testMethod:testMethod·p0.95    sample           0.004            s/opMyBenchmark.testMethod:testMethod·p0.99    sample           0.005            s/opMyBenchmark.testMethod:testMethod·p0.999   sample           0.008            s/opMyBenchmark.testMethod:testMethod·p0.9999  sample           0.009            s/opMyBenchmark.testMethod:testMethod·p1.00    sample           0.009            s/opMyBenchmark.testMethod                         ss     20    0.004 ±  0.001   s/op
复制代码

5000 条路由

Benchmark                                    Mode   Cnt    Score    Error  UnitsMyBenchmark.testMethod                      thrpt    20  248.353 ±  4.634  ops/sMyBenchmark.testMethod                       avgt    20    0.008 ±  0.001   s/opMyBenchmark.testMethod                     sample  5007    0.008 ±  0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample          0.002            s/opMyBenchmark.testMethod:testMethod·p0.50    sample          0.009            s/opMyBenchmark.testMethod:testMethod·p0.90    sample          0.009            s/opMyBenchmark.testMethod:testMethod·p0.95    sample          0.011            s/opMyBenchmark.testMethod:testMethod·p0.99    sample          0.014            s/opMyBenchmark.testMethod:testMethod·p0.999   sample          0.017            s/opMyBenchmark.testMethod:testMethod·p0.9999  sample          0.019            s/opMyBenchmark.testMethod:testMethod·p1.00    sample          0.019            s/opMyBenchmark.testMethod                         ss    20    0.009 ±  0.001   s/op
复制代码

1w 条路由

Benchmark                                    Mode   Cnt    Score    Error  UnitsMyBenchmark.testMethod                      thrpt    20  131.585 ±  1.799  ops/sMyBenchmark.testMethod                       avgt    20    0.015 ±  0.001   s/opMyBenchmark.testMethod                     sample  2671    0.015 ±  0.001   s/opMyBenchmark.testMethod:testMethod·p0.00    sample          0.002            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.028            s/opMyBenchmark.testMethod:testMethod·p0.999   sample          0.030            s/opMyBenchmark.testMethod:testMethod·p0.9999  sample          0.030            s/opMyBenchmark.testMethod:testMethod·p1.00    sample          0.030            s/opMyBenchmark.testMethod                         ss    20    0.016 ±  0.001   s/op
复制代码

10W 条路由

Benchmark                                    Mode  Cnt   Score   Error  UnitsMyBenchmark.testMethod                      thrpt   20  13.880 ± 0.364  ops/sMyBenchmark.testMethod                       avgt   20   0.141 ± 0.002   s/opMyBenchmark.testMethod                     sample  300   0.141 ± 0.002   s/opMyBenchmark.testMethod:testMethod·p0.00    sample        0.005           s/opMyBenchmark.testMethod:testMethod·p0.50    sample        0.141           s/opMyBenchmark.testMethod:testMethod·p0.90    sample        0.142           s/opMyBenchmark.testMethod:testMethod·p0.95    sample        0.143           s/opMyBenchmark.testMethod:testMethod·p0.99    sample        0.144           s/opMyBenchmark.testMethod:testMethod·p0.999   sample        0.270           s/opMyBenchmark.testMethod:testMethod·p0.9999  sample        0.270           s/opMyBenchmark.testMethod:testMethod·p1.00    sample        0.270           s/opMyBenchmark.testMethod                         ss   20   0.141 ± 0.001   s/op
复制代码


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

Aaron

关注

老骥伏枥 2019.06.29 加入

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

评论 (1 条评论)

发布
用户头像
测试结果,性能指标整体偏低,是由于为了规避可能带来的其他影响变量,httpclient每次请求都是单独创建客户端和链接的,没有进行复用,这部分性能消耗较大,但这部分是恒定的,不影响对测试目标的研究。
2021 年 06 月 13 日 10:31
回复
没有更多了
SpringCloud Gateway 路由数量对性能的影响研究