Skip to content

2. Benchmark性能测试报告

wangzheng0822 edited this page Jul 9, 2018 · 15 revisions

测试目的:

测试基于内存限流和基于Redis的分布式限流的性能表现,为不同的性能要求的应用场景提供数据支持。同时,作为benchmark,提供性能优化的依据。

性能预期:

性能指标及预期 关注在高并发下的TPS及其响应时间

限流 并发请求数 期望TPS 期望响应时间percentile999 是否满足
基于内存限流 300 10万 <1ms
分布式限流 300 1万 <10ms

关注资源及使用情况预期(非量化):

  • 硬件资源:CPU, 内存,磁盘,网络等资源使用合理及稳定。
  • JVM: 内存占用合理,GC稳定不频繁。

基于内存的限流,需要特别关注:
因为接口访问的计数器维护在内存中,需要注意计数器对内存的占用是否合理。

基于Redis的分布式限流,需要特别关注: Redis内存占用是否合理,CPU利用是否合理。

测试环境:

硬件资源配置:

CPU 6核 Intel(R) Core(TM) i7-2720QM CPU @ 2.20GHz  内存16GB

系统linux内核版本:

Linux 4.2.0-27-generic

Java版本如下:

java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

JVM配置参数如下:

-server -Xmx4g -Xms4g -Xmn256m -XX:PermSize=256m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseFastAccessorMethods -XX:+PrintGC -XX:+PrintGCDetails  -XX:+PrintGCDateStamps -Xloggc:./gc.log

基于内存的rate limiter测试场景:

场景一: 对同一个接口请求限流,不同的并发请求下,测试限流接口的TPS和响应时间。

场景具体说明:

  • 限流配置中只配置了一个接口,如下:appId: app1 api: /user limit: 10,000,000
  • 所有的请求都是访问http://www.test.com/user同一个接口
  • 线程内两次限流请求之间无thinking time
  • 事先10万次限流请求预热JVM

测试用例的测试目的:

测试代码在不同并发及高并发下的性能情况;

结果分析:

原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中T=N表示并发请求的线程数目;%9999表示99.99%响应时间;时间单位为毫秒。

t1

t1-diagram-1

t1-diagram-4

t1-diagram-3

在并发应用场景下,性能稳定,999%响应时间在10微妙,远低于预期1毫秒。TPS稳定在200万/秒左右。足以应对绝大多数高并发高性能场景。对于普通应用来说,关注999指标就够了,但是对于访问量巨大的应用来说,如果关注9999%线或者max最差响应时间,需要使用者自行测试考量是否适合。

线程>=CPU核个数(6)时,CPU使用率接近600%,CPU出现瓶颈,限制了TPS继续提高,说明基于内存的限流是CPU密集型的。

内存占用不多平稳,后面会有专门的测试场景测试内存的占用情况。

因为不涉及网络和磁盘操作,所以暂不考虑这两个的使用情况。

场景二:对不同的接口请求限流,相同并发请求下,测试限流接口的TPS和响应时间。

场景具体说明:

  • 并发数=6,设置为6的原因是:我们的机器CPU为6核,代码本身是计算密集型的,为了不达到CPU硬件性能瓶颈,以免影响测试结果。
  • 接口数不同,接口请求随机分散。
  • 每次请求之间无thinking time
  • 事先10万次限流请求预热JVM

测试目的:

测试计数器并发请求下的性能表现

结果与分析:

原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中U=N表示限流接口个数;%9999表示99.99%响应时间;时间单位为毫秒。

t2

计数器的并发竞争并不会对性能有太大影响。

场景三:引入thinking time,在高并发下,测试限流接口的响应时间。

场景具体说明:

  • 并发数=100
  • 接口数=100,接口请求随机分散
  • 事先10万次限流请求预热JVM

测试目的:

  • 引入thinking time更加接近真实的使用场景,关注引入thinking time,在相同的高并发下,是否响应时间大大减少?

结果与分析:

原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中D=N表示thinking time;%9999表示99.99%响应时间;时间单位均为毫秒。

t3

从上面看到,thinking time>=1ms之后,CPU使用率降低,响应时间明显好很多!在某些集成场景下,并不仅仅包含限流功能代码,所以thinking time引入的测试更加能贴近这些真实场景。

场景四:测试限流模块(规则与请求计数器)对内存的占用情况。

场景具体说明:

限流规则包含1万个接口,执行100万次限流请求,请求都是限流规则中配置的接口,然后执行heap dump,通过工具(如MAT)分析限流规则及请求计数器占用的retained size.

结果与分析:

  • 1万条接口限流规则占用了大约2.5MB内存空间;
  • 1万条接口请求100万次限流,接口请求计数器占用的内存空间大约2.5MB; 所以,接口限流规则和请求计数器占用的内存空间几乎可以忽略。

场景五:稳定性测试

场景具体说明:

100线程并发请求,每个请求的thinking time=1ms, 整个的请求压力大约在TPS=10万次/秒,持续运行半个小时,看JVM内存等各个资源消耗情况是否平稳。

1


2


3

结果与分析:

  • JVM内存占用:内存占用平稳,小于200MB.
  • GC频率与停顿时间: 平均2s一次minor gc, minor gc平均耗时10ms,回收的比较彻底,无full GC
  • CPU占用:利用率160%(6核),load average: 3左右,CPU负载小
  • 网络,IO: 无消耗

基于Redis的分布式rate limiter测试场景

场景一: 对同一个接口请求限流,不同的并发下,测试限流接口的TPS和响应时间。

场景具体说明:

  • 测试代码与Redis部署在同一台机器上
  • Redis连接池设置等于并发线程数
  • Redis数据不落硬盘
  • 限流配置中只配置了一个接口,如下:appId: app1 api: /user limit: 10,000,000
  • 所有的请求都是访问http://www.test.com/user同一个接口
  • 线程内两次限流请求之间无thinking time
  • 事先10万次限流请求预热JVM

测试场景设计的目的:

  • 测试代码在不同并发及高并发下的性能情况;

结果与分析:

原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中T=N表示并发请求的线程数目;%9999表示99.99%响应时间;时间单位为毫秒。

d1

d1-diagram-1

并发与响应时间的关系:

d2

我们看到1个线程压测的时候,tps在2万左右,3个线程之后就达到5万保持不变,说明三个线程并发发送lua限流脚本就可以打满redis,redis全力执行lua脚本的性能即为5万tps左右。测试并发线程数过高之后,会导致redis无法及时执行限流脚本排队,导致响应时间逐步升高。所以,初步结论是: 单Redis执行限流,只能做到5万TPS 基于Redis的限流对高并发的支持并不好,高并发会导致响应时间显著升高

但是,这里需要做两点说明:

  • 多个接口的限流可以通过redis集群分片存储的方式提高性能;
  • 本测试场景,没有thinking time和网络latency,所以在真实场景中,对并发的支持会更好些。

场景二:相同并发请求下,对不同的接口请求限流,测试限流接口的TPS和响应时间。

场景具体说明:

  • 测试代码与Redis部署在同一台机器上
  • Redis连接池设置等于并发线程数
  • Redis数据不落硬盘
  • 并发数=12,此时根据上面的场景一的测试,并未出现CPU瓶颈
  • 接口数不同,接口请求随机分散
  • 每个请求之间无thinking time
  • 事先10万次限流请求预热JVM

测试场景设计的目的:

  • 测试Redis计数器并发请求下的性能表现

结果分析:

原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中U=N表示接口数目;%9999表示99.99%响应时间;时间单位为毫秒。

d2

多个接口并发限流并没有提高tps和响应时间,同时也应征了:Redis执行命令是单线程工作模式。

场景三:引入thinking time,在高并发下,测试限流接口的响应时间。

场景具体说明:

  • 测试代码与Redis部署在同一台机器上
  • Redis连接池设置等于并发线程数
  • Redis数据不落硬盘
  • 并发数=100
  • 接口数=100,接口请求随机分散。
  • Thinking time从1ms开始逐渐增加
  • 事先10万次限流请求预热JVM

测试场景设计的目的:

  • 引入thinking time更加接近真实的使用场景,关注引入thinking time,在相同的高并发下,是否响应时间大大减少?

结果与分析:

原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中D=N表示thinking time;%9999表示99.99%响应时间;时间单位均为毫秒。

d3

为了更加直观,做了个图: d3-diagram-1

d3-diagram-2

随着thinking time的加大,高并发下响应时间逐渐递减。因为分布式限流的瓶颈主要来自于Redis,随着thinking time的加大,并发线程的请求频率减少,对redis的压力减小,排队等待执行的请求减少,所以整体的响应时间减少!也应征了我们前面说的,真实的线上场景,在可接受的响应时间内,分布式限流可以支持更高的并发。

场景四:测试请求计数器对Redis存储的消耗情况。

场景具体说明:

限流规则包含1万个接口,执行100万次限流请求,请求都是限流规则中配置的接口,然后执行heap dump,通过工具(如MAT)分析规则占用的内存,通过Redis本身的info命令统计内存的占用。

结果与分析:

  • 1万条限流规则在内存中只占用了2.5MB左右的空间。
  • 针对这1万个接口的100万次请求中峰值占用约1MB空间。

所以对内存和Redis的消耗完全可以忽略。

场景五:稳定性测试

场景具体说明:

100线程并发请求,每个请求的thinking time=5ms, 整个的请求压力大约在2万次/秒,持续运行半个小时,看JVM内存等各个资源消耗情况是否合理平稳。

1

2

3

结果与分析:

  • JVM内存占用:内存占用平稳,小于200MB.
  • GC频率与停顿时间: 平均6s一次minor gc, minor gc平均耗时10ms,回收的比较彻底,无full GC
  • CPU占用:利用率80%(6核),load average: 2左右,CPU负载小
  • Redis占用:内存消耗小于10MB,CPU利用率50%
  • 网络,IO: 无消耗

特别说明:

限于时间和资源,以上测试,测试代码与Redis都是部署在同一台机器的,所以未测试网络latency对限流性能的影响。独立部署Redis会引入网络latency(一般内网1,2ms,视具体网络环境),会导致响应时间响应增加。

总结分析:

基于内存的限流从测试及其代码看,是CPU密集型操作,压测过程中,再未出现代码层瓶颈之前CPU已经出现瓶颈,受限于测试机器,只有一台6核CPU的测试机,如果在更多核的机器上,表现应该优于本测试结果。

基于Redis的分布式限流受限于Redis的性能,TPS并不高(以上测试环境<5万),在应用场景上面比较受限,如果对性能要求比较高,请综合考虑使用。

备注说明:

本测试报告受限于硬件资源有限,测试结果仅限特定硬件条件下,如开头所示,如果要将本项目应用于生产环境,请针对要部署的机型及集成环境做性能测试,测试代码如下RateLimiterBenchmarkTest.java

Clone this wiki locally