长期处于CRUD工作中的我突然有一天关心起自己项目的 qps
了.便用 jmeter
测试了访问量最大的接口,发现只有可怜的 17r/s
左右......看到网络上比比皆是的几百万 qps
,我无地自容啊.
于是就准备进行性能优化了,不过在优化前我们需要进行性能测试,这样才能知道优化的效果如何.比如我第一步就是用 redis
加了缓存,结果测试发现居然比不加之前还要慢???所以性能测试是非常重要的一环,而 jmh
就是非常适合的性能测试工具了.
准备工作非常的简单,引入 jmh
的 maven
包就可以了.
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.22</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.22</version> <scope>provided</scope> </dependency> 复制代码
package jmh; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; /** * Benchmark * * @author wangpenglei * @since 2019/11/27 13:54 */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class Benchmark { public static void main(String[] args) throws Exception { // 使用一个单独进程执行测试,执行5遍warmup,然后执行5遍测试 Options opt = new OptionsBuilder().include(Benchmark.class.getSimpleName()).forks(1).warmupIterations(5) .measurementIterations(5).build(); new Runner(opt).run(); } @Setup public void init() { } @TearDown public void down() { } @org.openjdk.jmh.annotations.Benchmark public void test() { } } 复制代码
这个注解决定了测试模式,具体的内容网络上非常多,我这里采用的是 计算平均运行时间
这个注解是最后输出结果时的单位.因为测试的是接口,所以我采用的是毫秒.如果是测试本地 redis
或者本地方法这种可以换更小的单位.
这个注解定义了给定类实例的可用范围,因为 spring
里的 bean
默认是单例,所以我这里采用的是 运行相同测试的所有线程将共享实例。可以用来测试状态对象的多线程性能(或者仅标记该范围的基准)
.
非常简单的注解,平常测试都有的 测试前初始化 * 测试后清理资源 ** 测试方法 *.
因为我们需要 spring
的环境才能测试容器里的 bean
,所以需要在初始化方法中手动创建一个.我查了一下资料没发现什么更好的方法,就先自己手动创建吧.
private ConfigurableApplicationContext context; private AppGoodsController controller; @Setup public void init() { // 这里的WebApplication.class是项目里的spring boot启动类 context = SpringApplication.run(WebApplication.class); // 获取需要测试的bean this.controller = context.getBean(AppGoodsController.class); } @TearDown public void down() { context.close(); } 复制代码
写好测试方法后启动 main
方法就开始测试了,现在会报一些奇奇怪怪的错误,不过不影响结果我就没管了.运行完成后会输出结果,这时候可以对比下优化的效果.
Result "jmh.Benchmark.testGetGoodsList": 65.969 ±(99.9%) 10.683 ms/op [Average] (min, avg, max) = (63.087, 65.969, 69.996), stdev = 2.774 CI (99.9%): [55.286, 76.652] (assumes normal distribution) # Run complete. Total time: 00:02:48 REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark Mode Cnt Score Error Units Benchmark.testGetGoodsList avgt 5 65.969 ± 10.683 ms/op Process finished with exit code 0 复制代码
在文章开头我讲了一个负优化的例子,我用 redis
加了缓存后居然比直接数据库查询还要慢!其实原因很简单,我在本地电脑上测试,连接的 redis
却部署在服务器上.这样来回公网的网络延迟就已经很大了.不过数据库也是通过公网的,也不会比 redis
快才对.最后的原因是发现部署 redis
的服务器带宽只有 1m
也就是 100kb/s
,很容易就被占满了.最后优化是 redis
加缓存与使用内网连接 redis
.
Result "jmh.Benchmark.testGetGoodsList": 102.419 ±(99.9%) 153.083 ms/op [Average] (min, avg, max) = (65.047, 102.419, 162.409), stdev = 39.755 CI (99.9%): [≈ 0, 255.502] (assumes normal distribution) # Run complete. Total time: 00:03:03 REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark Mode Cnt Score Error Units Benchmark.testGetGoodsList avgt 5 102.419 ± 153.083 ms/op Process finished with exit code 0 复制代码
redis
速度,连的是本地 redis
): Result "jmh.Benchmark.testGetGoodsList": 29.210 ±(99.9%) 2.947 ms/op [Average] (min, avg, max) = (28.479, 29.210, 30.380), stdev = 0.765 CI (99.9%): [26.263, 32.157] (assumes normal distribution) # Run complete. Total time: 00:02:49 REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark Mode Cnt Score Error Units Benchmark.testGetGoodsList avgt 5 29.210 ± 2.947 ms/op Process finished with exit code 0 复制代码
可以看到大约快了 3.5
倍,其实还有优化空间,全部数据库操作都通过 redis
缓存的话,大概 1ms
就处理完了.