⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Performance-Testing/Dubbo-benchmark/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概述

在 2019.05.21 号,在经历了 1 年多的孵化,Dubbo 终于迎来了 Apache 毕业。在这期间,Dubbo 做了比较多的功能迭代,提供了 NodeJS、Python、Go 等语言的支持,也举办了多次社区活动,在网上的“骂声”也少了。

作为一个长期使用,并且坚持使用 Dubbo 的开发者,还是比较愉快的。可能,又经历了一次技术正确的选择。当然,更愉快的是,Spring Cloud Alibaba 貌似,也孵化的差不多,双剑合并,biubiubiu 。

本文,我们就来对 Dubbo 做一次性能基准测试。当写下这句话,突然想到了徐大sao:“今天天气不错,所以来吃顿好的”。

2. 性能指标

在 Dubbo 官方团队提供的 《Dubbo 性能测试报告》 的文章里,我们比较明确的可以看到希望的性能指标:

场景名称 对应指标名称 期望值范围 实际值 是否满足期望(是/否)
1k数据 响应时间 0.9ms 0.79ms
1k数据 TPS 10000 11994

3. 测试工具

目前可用于 Dubbo 测试的工具如下:

考虑到测试的简便性,以及学习成本(大多数人不会使用 JMeter),所以我们采用 dubbo-benchmark ,虽然说 JMH 也好多人不会。但是,因为 dubbo-benchmark 提供了开箱即用的脚本,即使不了解 JMH ,也能很方便的快速上手。当然,还是希望胖友能去了解下 JMH ,毕竟是 Java 微基准测试框架,可以用来测试我们编写的很多代码的性能。

4. dubbo-benchmark

4.1 项目结构

在开始正式测试之前,我们先来了解下 dubbo-benchmark 项目的大体结构。项目结构

分了比较多的 Maven 模块,我们将它们的关系,重新梳理如下图:项目层级

第一层 benchmark-base

提供 Dubbo Service 的实现,如下图:benchmark-base

  • UserService 类中,定义了我们业务场景中常用的四种方法:

    public interface UserService {

    public boolean existUser(String email);

    public boolean createUser(User user);

    public User getUser(long id);

    public Page<User> listUser(int pageNo);

    }

  • AbstractClient,理论来说,应该放到 client-base 中,可能迷路了。

第二层 client-base

实现 Dubbo 消费端的,基于 JMH ,实现 Benchmark 基类。重点在 benchmark.Client 类,代码如下:

private static final int CONCURRENCY = 32;

public static void main(String[] args) throws Exception {
Options opt;
ChainedOptionsBuilder optBuilder = new OptionsBuilder()
// benchmark 所在的类名,此处就是 Client
.include(Client.class.getSimpleName())
// 预热 3 轮,每轮 10 秒
.warmupIterations(3)
.warmupTime(TimeValue.seconds(10))
// 测量(测试)3 轮,每轮 10 秒
.measurementIterations(3)
.measurementTime(TimeValue.seconds(10))
// 并发线程数为 32
.threads(CONCURRENCY)
// 进行 fork 的次数。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。
.forks(1);

// 设置报告结果
opt = doOptions(optBuilder).build();

new Runner(opt).run();
}

private static ChainedOptionsBuilder doOptions(ChainedOptionsBuilder optBuilder) {
String output = System.getProperty("benchmark.output");
if (output != null && !output.trim().isEmpty()) {
optBuilder.output(output);
}
return optBuilder;
}

在 Client 类中,定义了对 UserService 调用的四个 Benchmark 方法,代码如下:

private final ClassPathXmlApplicationContext context;
private final UserService userService;

public Client() {
// 读取 consumer.xml 配置文件,并启动 Spring 容器。这个配置文件,由子项目配置
context = new ClassPathXmlApplicationContext("consumer.xml");
context.start();
// 获得 UserService Bean
userService = (UserService) context.getBean("userService");
}

@Override
protected UserService getUserService() {
return userService;
}

@TearDown
public void close() throws IOException {
ProtocolConfig.destroyAll();
context.close();
}

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Override
public boolean existUser() throws Exception {
return super.existUser();
}

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Override
public boolean createUser() throws Exception {
return super.createUser();
}

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Override
public User getUser() throws Exception {
return super.getUser();
}

@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Override
public Page<User> listUser() throws Exception {
return super.listUser();
}

第二层 server-base

实现 Dubbo 消费端的,启动 Dubbo 服务。重点在 benchmark.Server 类,代码如下:

public class Server {

public static void main(String[] args) throws InterruptedException {
// 读取 provider.xml 配置文件,并启动 Spring 容器。这个配置文件,由子项目配置
try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml")) {
context.start();
// sleep ,防止进程结束
Thread.sleep(Integer.MAX_VALUE);
}
}

}

  • 因为是被测方,所以无需集成到 JMH 中。

第三层 {protocol}-{serialize}-client

具体协议( Protocol ),使用具体序列化( Serialize ) 方式的消费者。

第四层 {protocol}-{serialize}-server

具体协议( Protocol ),使用具体序列化( Serialize ) 方式的提供者。

4.2 测试环境

  • 型号 :ecs.c5.xlarge

    艿艿:和我一样抠门(穷)的胖友,可以买竞价类型服务器,使用完后,做成镜像。等下次需要使用的时候,恢复一下。HOHO 。

  • 系统 :CentOS 7.6 64位

  • CPU :4 核

  • 内存 :8 GB

  • 磁盘 :40 GB ESSD 云盘

  • Java :OpenJDK Runtime Environment (build 1.8.0_212-b04)

  • Dubbo :2.6.1

    虽然 Dubbo 项目本身已经完成孵化,但是 dubbo-benchmark 并未更新到最新版本的 2.7.2 。所以,本文还是测试 Dubbo 2.6.1 版本。当然,这个对测试结果影响不大,妥妥的。

4.3 安装 dubbo-benchmark

第一步,克隆项目

git clone https://github.com/apache/dubbo-benchmark.git
cd dubbo-benchmark

第二步,启动服务提供者

sh benchmark.sh dubbo-kryo-server

会有一个编译的过程,耐心等待。

第三步,启动服务消费者

需要新启一个终端

sh benchmark.sh dubbo-kryo-client

开始 JMH 测试...整个测试过程,持续 15 分钟左右。

4.4 dubbo-hessianlite

本小节,我们来测试 dubbo-hessianlite-client 和 dubbo-hessianlite-server 。

这个组合,是我们使用 Dubbo 最主流的方式。

  • 协议:Dubbo
  • 序列化:hessian-lite ,Dubbo 对 Hessian 提供的序列化方式的性能优化和 Bug 修复。
  • 通信:Netty4

第一步,启动服务提供者

sh benchmark.sh dubbo-hessianlite-server

第二步,启动服务消费者

需要新启一个终端

sh benchmark.sh dubbo-hessianlite-client

🔥 测试结果

Benchmark                               Mode      Cnt   Score   Error   Units
Client.createUser thrpt 3 16.887 ? 1.729 ops/ms
Client.existUser thrpt 3 47.293 ? 4.993 ops/ms
Client.getUser thrpt 3 19.698 ? 8.588 ops/ms
Client.listUser thrpt 3 3.457 ? 0.180 ops/ms
Client.createUser avgt 3 1.416 ? 0.308 ms/op
Client.existUser avgt 3 0.678 ? 0.038 ms/op
Client.getUser avgt 3 1.657 ? 0.359 ms/op
Client.listUser avgt 3 9.299 ? 0.872 ms/op
Client.createUser sample 499898 1.918 ? 0.007 ms/op
Client.createUser:createUser?p0.00 sample 0.279 ms/op
Client.createUser:createUser?p0.50 sample 1.448 ms/op
Client.createUser:createUser?p0.90 sample 2.613 ms/op
Client.createUser:createUser?p0.95 sample 3.027 ms/op
Client.createUser:createUser?p0.99 sample 9.732 ms/op
Client.createUser:createUser?p0.999 sample 16.876 ms/op
Client.createUser:createUser?p0.9999 sample 28.280 ms/op
Client.createUser:createUser?p1.00 sample 39.453 ms/op
Client.existUser sample 1376160 0.697 ? 0.002 ms/op
Client.existUser:existUser?p0.00 sample 0.094 ms/op
Client.existUser:existUser?p0.50 sample 0.647 ms/op
Client.existUser:existUser?p0.90 sample 0.842 ms/op
Client.existUser:existUser?p0.95 sample 0.921 ms/op
Client.existUser:existUser?p0.99 sample 1.425 ms/op
Client.existUser:existUser?p0.999 sample 10.355 ms/op
Client.existUser:existUser?p0.9999 sample 16.145 ms/op
Client.existUser:existUser?p1.00 sample 24.773 ms/op
Client.getUser sample 568869 1.686 ? 0.006 ms/op
Client.getUser:getUser?p0.00 sample 0.262 ms/op
Client.getUser:getUser?p0.50 sample 1.436 ms/op
Client.getUser:getUser?p0.90 sample 1.954 ms/op
Client.getUser:getUser?p0.95 sample 2.609 ms/op
Client.getUser:getUser?p0.99 sample 9.634 ms/op
Client.getUser:getUser?p0.999 sample 15.862 ms/op
Client.getUser:getUser?p0.9999 sample 31.217 ms/op
Client.getUser:getUser?p1.00 sample 44.302 ms/op
Client.listUser sample 103394 9.272 ? 0.038 ms/op
Client.listUser:listUser?p0.00 sample 1.792 ms/op
Client.listUser:listUser?p0.50 sample 9.060 ms/op
Client.listUser:listUser?p0.90 sample 14.287 ms/op
Client.listUser:listUser?p0.95 sample 15.679 ms/op
Client.listUser:listUser?p0.99 sample 17.336 ms/op
Client.listUser:listUser?p0.999 sample 30.966 ms/op
Client.listUser:listUser?p0.9999 sample 38.161 ms/op
Client.listUser:listUser?p1.00 sample 45.351 ms/op

4.5 dubbo-fst

本小节,我们来测试 dubbo-fst-client 和 dubbo-fst-server 。

这个组合,是我们使用 Dubbo 最主流的方式。

  • 协议:Dubbo
  • 序列化:FST
  • 通信:Netty4

第一步,启动服务提供者

sh benchmark.sh dubbo-fst-server

第二步,启动服务消费者

需要新启一个终端

sh benchmark.sh dubbo-fst-client

🔥 测试结果

Benchmark                               Mode      Cnt   Score    Error   Units
Client.createUser thrpt 3 44.810 ? 5.152 ops/ms
Client.existUser thrpt 3 53.153 ? 49.787 ops/ms
Client.getUser thrpt 3 41.754 ? 8.210 ops/ms
Client.listUser thrpt 3 14.791 ? 3.458 ops/ms
Client.createUser avgt 3 0.719 ? 0.080 ms/op
Client.existUser avgt 3 0.574 ? 0.434 ms/op
Client.getUser avgt 3 0.703 ? 0.045 ms/op
Client.listUser avgt 3 2.189 ? 0.353 ms/op
Client.createUser sample 1267915 0.756 ? 0.002 ms/op
Client.createUser:createUser?p0.00 sample 0.099 ms/op
Client.createUser:createUser?p0.50 sample 0.678 ms/op
Client.createUser:createUser?p0.90 sample 0.888 ms/op
Client.createUser:createUser?p0.95 sample 1.034 ms/op
Client.createUser:createUser?p0.99 sample 3.383 ms/op
Client.createUser:createUser?p0.999 sample 10.502 ms/op
Client.createUser:createUser?p0.9999 sample 22.650 ms/op
Client.createUser:createUser?p1.00 sample 35.979 ms/op
Client.existUser sample 1461428 0.656 ? 0.002 ms/op
Client.existUser:existUser?p0.00 sample 0.077 ms/op
Client.existUser:existUser?p0.50 sample 0.504 ms/op
Client.existUser:existUser?p0.90 sample 1.128 ms/op
Client.existUser:existUser?p0.95 sample 1.516 ms/op
Client.existUser:existUser?p0.99 sample 2.802 ms/op
Client.existUser:existUser?p0.999 sample 6.452 ms/op
Client.existUser:existUser?p0.9999 sample 33.358 ms/op
Client.existUser:existUser?p1.00 sample 58.262 ms/op
Client.getUser sample 1270938 0.755 ? 0.003 ms/op
Client.getUser:getUser?p0.00 sample 0.084 ms/op
Client.getUser:getUser?p0.50 sample 0.588 ms/op
Client.getUser:getUser?p0.90 sample 1.034 ms/op
Client.getUser:getUser?p0.95 sample 1.626 ms/op
Client.getUser:getUser?p0.99 sample 4.473 ms/op
Client.getUser:getUser?p0.999 sample 10.830 ms/op
Client.getUser:getUser?p0.9999 sample 27.719 ms/op
Client.getUser:getUser?p1.00 sample 45.875 ms/op
Client.listUser sample 442763 2.166 ? 0.009 ms/op
Client.listUser:listUser?p0.00 sample 0.306 ms/op
Client.listUser:listUser?p0.50 sample 1.767 ms/op
Client.listUser:listUser?p0.90 sample 3.039 ms/op
Client.listUser:listUser?p0.95 sample 4.415 ms/op
Client.listUser:listUser?p0.99 sample 11.551 ms/op
Client.listUser:listUser?p0.999 sample 21.045 ms/op
Client.listUser:listUser?p0.9999 sample 32.702 ms/op
Client.listUser:listUser?p1.00 sample 45.089 ms/op

4.6 dubbo-kryo

本小节,我们来测试 dubbo-kryo-client 和 dubbo-kryo-server 。

  • 协议:Dubbo
  • 序列化:Kryo
  • 通信:Netty4

第一步,启动服务提供者

sh benchmark.sh dubbo-kryo-server

第二步,启动服务消费者

需要新启一个终端

sh benchmark.sh dubbo-kryo-client

🔥 测试结果

Benchmark                               Mode      Cnt   Score   Error   Units
Client.createUser thrpt 3 33.678 ? 2.656 ops/ms
Client.existUser thrpt 3 50.030 ? 3.509 ops/ms
Client.getUser thrpt 3 34.125 ? 4.886 ops/ms
Client.listUser thrpt 3 11.929 ? 1.746 ops/ms
Client.createUser avgt 3 0.955 ? 0.164 ms/op
Client.existUser avgt 3 0.642 ? 0.051 ms/op
Client.getUser avgt 3 0.940 ? 0.071 ms/op
Client.listUser avgt 3 2.603 ? 0.748 ms/op
Client.createUser sample 985106 0.973 ? 0.003 ms/op
Client.createUser:createUser?p0.00 sample 0.148 ms/op
Client.createUser:createUser?p0.50 sample 0.855 ms/op
Client.createUser:createUser?p0.90 sample 1.147 ms/op
Client.createUser:createUser?p0.95 sample 1.315 ms/op
Client.createUser:createUser?p0.99 sample 5.300 ms/op
Client.createUser:createUser?p0.999 sample 12.517 ms/op
Client.createUser:createUser?p0.9999 sample 21.037 ms/op
Client.createUser:createUser?p1.00 sample 31.850 ms/op
Client.existUser sample 1470527 0.652 ? 0.001 ms/op
Client.existUser:existUser?p0.00 sample 0.092 ms/op
Client.existUser:existUser?p0.50 sample 0.601 ms/op
Client.existUser:existUser?p0.90 sample 0.800 ms/op
Client.existUser:existUser?p0.95 sample 0.876 ms/op
Client.existUser:existUser?p0.99 sample 1.550 ms/op
Client.existUser:existUser?p0.999 sample 9.650 ms/op
Client.existUser:existUser?p0.9999 sample 14.844 ms/op
Client.existUser:existUser?p1.00 sample 30.573 ms/op
Client.getUser sample 1001893 0.957 ? 0.004 ms/op
Client.getUser:getUser?p0.00 sample 0.127 ms/op
Client.getUser:getUser?p0.50 sample 0.741 ms/op
Client.getUser:getUser?p0.90 sample 1.401 ms/op
Client.getUser:getUser?p0.95 sample 2.191 ms/op
Client.getUser:getUser?p0.99 sample 5.546 ms/op
Client.getUser:getUser?p0.999 sample 12.059 ms/op
Client.getUser:getUser?p0.9999 sample 24.518 ms/op
Client.getUser:getUser?p1.00 sample 49.873 ms/op
Client.listUser sample 363958 2.636 ? 0.013 ms/op
Client.listUser:listUser?p0.00 sample 0.390 ms/op
Client.listUser:listUser?p0.50 sample 2.071 ms/op
Client.listUser:listUser?p0.90 sample 4.084 ms/op
Client.listUser:listUser?p0.95 sample 6.947 ms/op
Client.listUser:listUser?p0.99 sample 12.403 ms/op
Client.listUser:listUser?p0.999 sample 22.940 ms/op
Client.listUser:listUser?p0.9999 sample 40.935 ms/op
Client.listUser:listUser?p1.00 sample 60.097 ms/op

4.7 小结

可能有胖友,对 JMH 的结果报告不熟悉,这里简单介绍下:

  • Mode :thrpt ,throughput 的缩写,吞吐量,单位为:ops/ms ,所以需要乘以 1000 ,换算成 QPS 。
  • Mode :avgt ,AverageTime 的缩写,每个操作🎺的时间,单位为 ms 毫秒。
  • Mode :sample ,采样分布。每一行代表,代表百分之多少(?p)的请求,在多少毫秒内。

整理结果如下表:性能结果

  • 三个测试用例,差别是序列化使用的库,所以序列化的性能,决定了整体的性能结果。
  • 方法的性能排行是:existUser > getUser > createUser > listUser
  • 项目的性能排行是:dubbo-fst > dubbo-kryo > dubbo-hessianlite

当然,得到这样一个测试结果,我们很自然的会有一个疑惑,既然 FST 和 Kryo 性能比 Hessian Lite 好,为什么默认选择的还是 Hessian Lite 呢?因为在 RPC 场景下,我们有跨语言的诉求,而 FST 和 Kryo 是 Java 序列化的库,不支持跨语言,而 Hessian Lite 支持。

666. 彩蛋

因为 dubbo-benchmark 项目的存在,所以整个测试的过程,体验还是比较舒服,测试也是非常顺利的。当然,还是比较期待 dubbo-benchmark 后续能更新的更牛逼:

  • 例如说,支持更多的序列化方式,因为最新版本有了 Gson、Probufstuff 等新的序列化支持
  • 再例如说,支持除了 Dubbo 协议之外,Rest 等等。

也推荐下如下文章:

文章目录
  1. 1. 1. 概述
  2. 2. 2. 性能指标
  3. 3. 3. 测试工具
  4. 4. 4. dubbo-benchmark
    1. 4.1. 4.1 项目结构
    2. 4.2. 4.2 测试环境
    3. 4.3. 4.3 安装 dubbo-benchmark
    4. 4.4. 4.4 dubbo-hessianlite
    5. 4.5. 4.5 dubbo-fst
    6. 4.6. 4.6 dubbo-kryo
    7. 4.7. 4.7 小结
  5. 5. 666. 彩蛋