首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

听大牛来深度解析微服务高并发基础知识:Sentinel性能压测

Sentinel性能压测

“引入Sentinel带来的性能损耗非常小,只有在业务单机量级超过25万QPS的时候才会有一些显著的影响(5%~10%左右),单机QPS不太大的时候损耗几乎可以忽略不计。”这是Sentinel官方文档中的一句话,本节将通过基准测试验证这句话。

JMH基准测试

基准测试(Benchmark)是测量、评估软件性能指标的一种测试,用来对某个特定目标场景的某项性能指标进行定量的和可对比的测试。

JMH即Java Microbenchmark Harness,是Java用来做基准测试的一个工具。该工具由OpenJDK提供并维护,其测试结果的可信度高。

我们可以将JMH用于需要进行基准测试的项目中,以单元测试方式使用。

1. 添加依赖

在需要进行基准测试的项目中引入JMH的jar包,依赖配置如下:

注意:1.23版本是JMH目前最新的版本。

2. 使用注解方式

JMH提供了一系列注解,用于简化代码的编写。在运行时,注解配置被用于解析生成BenchmarkListEntry配置类实例。被@Benchmark注解注释的方法为基准测试方法,而注释在类上的注解或注释在类的字段上的注解,则是类中所有基准测试方法共用的配置。

1)@Benchmark

@Benchmark注解用于声明一个public方法为基准测试方法,@Benchmark注解的使用如下述代码所示:

2)@BenchmarkMode

使用JMH可以轻松地测试出某个接口的吞吐量、平均执行时间等指标的数据。

如果需要测试某个方法的平均耗时,那么可以使用@BenchmarkMode注解并指定基准测试的模式为AverageTime,代码如下:

常用的基准测试模式如下。

AverageTime:测量平均耗时。

Throughput:测量吞吐量。

3)@Measurement

@Measurement注解用于指定测量次数及每次测量的持续时间。测量次数越多且每次测量的持续时间越长,测试结果的可信度就越高。

@Measurement注解有如下3个配置项:

iterations:测量次数;

time与timeUnit:测量一次的持续时间;

timeUnit:用于指定时间单位。

@Measurement注解的使用如下述代码所示:

在本例中,我们配置总的测量次数为5,每次测量的持续时间为1秒。1秒内执行testFunction方法的次数是不固定的,由方法执行耗时和time决定。

每个线程实际执行基准测试方法的次数等于time除以基准测试方法单次执行的耗时。假设基准测试方法单次执行的耗时为1秒,并使用@Measurement注解指定iterations为100次、time为10秒,那么一次测量最多只能执行10(即10秒/1秒)次基准测试方法,而iterations为100次指的是测量100次。

注意:iterations指的是测量总次数,而不是执行基准测试方法的次数。

4)@Warmup

采用一定的预热次数可以提升测试结果的准确度,而使用@Warmup注解可以声明需要预热的次数及每次预热的持续时间。

@Warmup注解有如下3个配置项。

iterations:预热次数;

time与timeUnit:预热一次的持续时间;

timeUnit:用于指定时间单位。

@Warmup注解的使用如下述代码所示:

在本例中,我们配置总的预热次数为5,每次预热持续时间为1秒。

5)@OutputTimeUnit

@OutputTimeUnit注解用于指定输出方法执行耗时的单位。

如果方法执行耗时为毫秒级别,则为了便于观察结果,可以使用@OutputTimeUnit注解指定输出的耗时时间单位为毫秒,代码如下:

6)@Fork

@Fork注解用于指定Fork多少个子进程来执行同一基准测试方法。

如果不需要Fork多个子进程,则可以使用@Fork注解指定进程数为1,代码如下:

7)@Threads

@Threads注解用于指定使用多少个线程来执行基准测试方法。

如果使用@Threads注解指定线程数为2,则每次测量都会创建两个线程来执行基准测试方法,代码如下:

假设@Measurement注解指定time为1秒,则基准测试方法单次执行的耗时为1秒。若只使用单个线程测量,则一次测量将只会执行一次基准测试方法,而若使用10个线程测量,则一次测量将能执行10次基准测试方法。

@Threads注解可以用来模拟高并发,一般用于测量基准测试方法的吞吐量。

8)公共注解

如果需要在MyTestBenchmark类中创建两个基准测试方法——testFunction1和testFunction2,并且这两个方法分别调用不同的支付接口,用于对比两个接口的性能,那么可以将除@Benchmark注解外的其他注解都声明到类上,让两个基准测试方法都使用同样的配置,代码如下:

我们可以使用JMH对JSON序列化框架进行基准测试。以测量分别使用Gson、Jackson反序列化同一JSON字符串的平均耗时为例,编写基准测试用例,代码如下:

在本例中,使用@Threads注解声明创建两个线程来执行基准测试方法,使用@State注解指定gsonParser、jacksonParser这两个字段的共享域为Scope.Thread,即在不同线程中,gsonParser、jacksonParser这两个字段都是不同的实例。

以testGson方法为例,我们可以认为JMH会为每个线程克隆出一个gsonParser对象。如果在testGson方法中打印gsonParser对象的hashCode,就会发现相同线程打印的结果相同,不同线程打印的结果不同,代码如下:

执行testGson方法输出的结果如下:

9)@Param

使用@Param注解可以指定基准测试方法的执行参数,并且只能指定String类型的值。该参数值可以是一个数组,将在程序运行期间按给定顺序被遍历。

如果使用@Param注解指定了多个参数值,则JMH会为每个参数值执行一次基准测试。

例如,测试不同复杂度的JSON字符串使用Gson框架与使用Jackson框架解析的性能对比,代码如下:

测试结果输出如下:

3. 使用非注解方式

使用注解与不使用注解在本质上并没有区别,只是使用注解更加方便。在运行时,注解配置被用于解析生成BenchmarkListEntry配置类实例,而不使用注解最终也需要构造BenchmarkListEntry配置类实例。每一个基准测试方法对应一个BenchmarkListEntry配置类实例。

Options实例用于配置基准测试参数,Runner实例用于执行基准测试方法。首先使用OptionsBuilder方法构造一个Options实例,并指定基准测试方法及执行基准测试方法的参数,然后创建Runner实例并调用Runner实例的run方法,即可执行基准测试。

使用非注解方式实现上面的例子,代码如下:

OptionsBuilder类提供的方法如下。

include:导入一个基准测试类,参数为类的简单名称,JMH默认会把include导入的类的每个public方法都当作基准测试方法。

exclude:排除不需要参与基准测试的方法。

forks:对应@Fork注解,指定Fork多少个子进程来执行同一基准测试方法。

threads:对应@Threads注解,指定使用多少个线程来执行基准测试方法。

timeUnit:对应@OutputTimeUnit注解,指定输出方法执行耗时的单位。

warmupIterations:对应@Warmup注解,指定预热次数。

warmupTime:对应@Warmup注解,指定每次预热的持续时间。

measurementIterations:对应@Measurement注解,指定测量次数。

measurementTime:对应@Measurement注解,指定每次测量的持续时间。

mode:对应@BenchmarkMode注解,指定测量模式。

4. 打包成jar包放到服务器上执行

我们可以使用单元测试方式对JSON解析框架进行性能对比。若想要测试Web服务的某个接口性能,则需要对接口进行压测,而不能使用简单的单元测试方式进行测试。因此我们可以独立创建一个接口测试项目,将基准测试代码写在该项目中,然后将写好的基准测试项目打包成jar包放到Linux服务器上执行,这样测试结果会更加准确。

使用java命令即可运行一个基准测试应用,代码如下。

5. 在IDEA中执行

在IDEA中,我们可以编写一个单元测试方法,并在单元测试方法中创建一个Runner实例,然后调用Runner实例的run方法执行基准测试。但JMH不会扫描包,不会执行每个基准测试方法,这就需要我们通过配置项来告知JMH需要执行哪些基准测试方法,代码如下。

完整代码如下:

由于本例中的JsonBenchmark类已经使用了注解,因此Options实例只需要配置需要执行基准测试的类即可。

如果需要执行多个基准测试类,则可以多次调用include方法;如果需要将测试结果输出到文件中,则可以调用output方法配置文件路径,否则输出到控制台中。

6. 在IDEA中使用JMH Plugin插件执行

安装JMH Plugin插件:在IDEA中搜索JMH Plugin,安装后重启即可使用。

1)只执行单个基准测试方法

在方法名称所在行中,IDEA会有一个▶执行符号,右击该符号并在弹出的快捷菜单中选择“运行”命令即可。

2)执行一个类中的所有基准测试方法

在类名所在行中,IDEA会有一个▶执行符号,右击该符号并在弹出的快捷菜单中选择“运行”命令即可,该类下的所有被@Benchmark注解注释的方法都会被执行。

注意:如果写的是单元测试方法,则IDEA会提示选择执行单元测试还是基准测试。

使用JMH进行Sentinel压测

如果要测试Sentinel对应用性能的影响,则需要测试两组数据并进行对比。这两组数据分别是不使用Sentinel的情况下方法的吞吐量及使用Sentinel的情况下方法的吞吐量。

Sentinel提供的基准测试类的部分源码如下:

该基准测试类通过@State注解来指定每个线程使用不同的numbers字段的实例,所以@Setup注解的方法也会执行8次,并分别在每个线程开始执行基准测试方法之前执行,用于完成初始化工作,与JUnit中的@Before注解功能相似。

分别对doSomething方法和doSomethingWithEntry方法进行基准测试,其中doSomething方法用于模拟业务方法,doSomethingWithEntry方法用于模拟使用Sentinel保护业务方法。将

准测试模式配置为吞吐量模式,使用@Warmup注解指定预热次数为10次,使用@OutputTimeUnit注解指定输出单位为秒,使用@Fork注解指定进程数为1个,使用@Threads注解指定线程数为8个。

使用doSomething方法进行吞吐量测试的结果如下:

由结果可知,最小OPS为295869.456,平均OPS为300948.682,最大OPS为316089.624。

使用doSomethingWithEntry方法进行吞吐量测试的结果如下。

由结果可知,最小OPS为280835.799,平均OPS为309934.827,最大OPS为337712.803。

其中,OPS表示每秒执行的操作次数或每秒执行的方法次数。

从本次测试结果可以看出,doSomething方法的平均吞吐量与doSomethingWithEntry方法的平均吞吐量相差约为3%,也就是说,在超过28万OPS(QPS)的情况下,Sentinel对应用性能的影响不到3%。在实际项目场景中,一个服务节点所有接口总的QPS也很难达到25万这个值,而QPS越低,Sentinel对应用性能的影响也越低。

但这毕竟是在没有配置任何限流规则、只有一个资源且调用链路的深度为1的情况下进行的测试,因此这个结果只能算一个理想的参考值,在实际项目场景中还是以项目实际的使用测试结果为准。

小结

通过本章的学习,读者对服务降级、限流、熔断、流量效果控制这些概念有了基本的了解,也了解了Sentinel的一些特性,学习了如何使用基础测试工具JMH,并使用JMH对Sentinel进行了简单场景下的性能压测。

本文给大家讲解的内容是深度解析微服务高并发基础知识:Sentinel性能压测

下篇文章给大家讲解的内容是度解析微服务高并发概念与核心类:了解Sentinel的一些概念

感谢大家的支持!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230206A0415V00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

相关快讯

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券