概述
背景描述
近期在公司建设API治理平台过程中,以SpringCloud Gateway为基础,构建了一个API的Mock服务,以API的URI作为路由,根据服务端存储的API DSL,验证请求信息,生成并返回Mock报文。
SpringCloud Gateway具备很好的 动态路由 支持功能,可以在API DSL创建的同时,创建一条Mock路由,这样API DSL创建后,开发人员就可以使用Mock服务进行开发调试工作。
但作为一个企业级应用,所管辖的API的数量众多,SpringCloud Gateway在路由表急剧膨胀后的性能如何?目前没有查阅到明确说明的资料。翻阅其源码发现,断言命中其实是进行的遍历,这样路由表膨胀时,路由性能存疑,需要进行验证,根据验证结果制定近一步优化方案。
验证方法
保持其他所有变量不变,仅调整路由表大小,使用JMH工具进行基准测试,得出路由表膨胀与路由性能的关系。
需要编写三个服务:
-
路由网关
-
挡板服务(路由转发的upstream)
-
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
@Document
public 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
@Component
public 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
@SpringBootTest
class 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 package
java -jar target/benchmarks.jar
收集测试结果
测试结果数据见下文,共6组。
测试数据
对照组(直连)
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 899.152 ± 137.062 ops/s
MyBenchmark.testMethod avgt 20 0.002 ± 0.001 s/op
MyBenchmark.testMethod sample 17647 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.003 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.012 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.018 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.018 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
100条路由
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 713.931 ± 97.302 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 14090 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.004 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.005 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.008 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.011 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.011 s/op
MyBenchmark.testMethod ss 20 0.003 ± 0.001 s/op
1000条路由
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 630.823 ± 41.301 ops/s
MyBenchmark.testMethod avgt 20 0.003 ± 0.001 s/op
MyBenchmark.testMethod sample 12617 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.004 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.004 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.005 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.008 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.009 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.009 s/op
MyBenchmark.testMethod ss 20 0.004 ± 0.001 s/op
5000条路由
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 248.353 ± 4.634 ops/s
MyBenchmark.testMethod avgt 20 0.008 ± 0.001 s/op
MyBenchmark.testMethod sample 5007 0.008 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.009 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.009 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.011 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.014 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.017 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.019 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.019 s/op
MyBenchmark.testMethod ss 20 0.009 ± 0.001 s/op
1w条路由
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 131.585 ± 1.799 ops/s
MyBenchmark.testMethod avgt 20 0.015 ± 0.001 s/op
MyBenchmark.testMethod sample 2671 0.015 ± 0.001 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.002 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.028 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.030 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.016 ± 0.001 s/op
10W条路由
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 20 13.880 ± 0.364 ops/s
MyBenchmark.testMethod avgt 20 0.141 ± 0.002 s/op
MyBenchmark.testMethod sample 300 0.141 ± 0.002 s/op
MyBenchmark.testMethod:testMethod·p0.00 sample 0.005 s/op
MyBenchmark.testMethod:testMethod·p0.50 sample 0.141 s/op
MyBenchmark.testMethod:testMethod·p0.90 sample 0.142 s/op
MyBenchmark.testMethod:testMethod·p0.95 sample 0.143 s/op
MyBenchmark.testMethod:testMethod·p0.99 sample 0.144 s/op
MyBenchmark.testMethod:testMethod·p0.999 sample 0.270 s/op
MyBenchmark.testMethod:testMethod·p0.9999 sample 0.270 s/op
MyBenchmark.testMethod:testMethod·p1.00 sample 0.270 s/op
MyBenchmark.testMethod ss 20 0.141 ± 0.001 s/op
原文链接:【https://xie.infoq.cn/article/d39fde1ce527ec2c3c6750c4c】
最后
以上就是贤惠鼠标为你收集整理的SpringCloud Gateway 路由数量对性能的影响研究的全部内容,希望文章能够帮你解决SpringCloud Gateway 路由数量对性能的影响研究所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复