-
Notifications
You must be signed in to change notification settings - Fork 382
2. Benchmark性能测试报告
测试基于内存限流和基于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
- 限流配置中只配置了一个接口,如下:appId: app1 api: /user limit: 10,000,000
- 所有的请求都是访问http://www.test.com/user同一个接口
- 线程内两次限流请求之间无thinking time
- 事先10万次限流请求预热JVM
测试代码在不同并发及高并发下的性能情况;
原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中T=N表示并发请求的线程数目;%9999表示99.99%响应时间;时间单位为毫秒。
在并发应用场景下,性能稳定,999%响应时间在10微妙,远低于预期1毫秒。TPS稳定在200万/秒左右。足以应对绝大多数高并发高性能场景。对于普通应用来说,关注999指标就够了,但是对于访问量巨大的应用来说,如果关注9999%线或者max最差响应时间,需要使用者自行测试考量是否适合。
线程>=CPU核个数(6)时,CPU使用率接近600%,CPU出现瓶颈,限制了TPS继续提高,说明基于内存的限流是CPU密集型的。
内存占用不多平稳,后面会有专门的测试场景测试内存的占用情况。
因为不涉及网络和磁盘操作,所以暂不考虑这两个的使用情况。
- 并发数=6,设置为6的原因是:我们的机器CPU为6核,代码本身是计算密集型的,为了不达到CPU硬件性能瓶颈,以免影响测试结果。
- 接口数不同,接口请求随机分散。
- 每次请求之间无thinking time
- 事先10万次限流请求预热JVM
测试计数器并发请求下的性能表现
原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中U=N表示限流接口个数;%9999表示99.99%响应时间;时间单位为毫秒。
计数器的并发竞争并不会对性能有太大影响。
- 并发数=100
- 接口数=100,接口请求随机分散
- 事先10万次限流请求预热JVM
- 引入thinking time更加接近真实的使用场景,关注引入thinking time,在相同的高并发下,是否响应时间大大减少?
原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中D=N表示thinking time;%9999表示99.99%响应时间;时间单位均为毫秒。
从上面看到,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内存等各个资源消耗情况是否平稳。
- JVM内存占用:内存占用平稳,小于200MB.
- GC频率与停顿时间: 平均2s一次minor gc, minor gc平均耗时10ms,回收的比较彻底,无full GC
- CPU占用:利用率160%(6核),load average: 3左右,CPU负载小
- 网络,IO: 无消耗
- 测试代码与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%响应时间;时间单位为毫秒。
并发与响应时间的关系:
我们看到1个线程压测的时候,tps在2万左右,3个线程之后就达到5万保持不变,说明三个线程并发发送lua限流脚本就可以打满redis,redis全力执行lua脚本的性能即为5万tps左右。测试并发线程数过高之后,会导致redis无法及时执行限流脚本排队,导致响应时间逐步升高。所以,初步结论是: 单Redis执行限流,只能做到5万TPS 基于Redis的限流对高并发的支持并不好,高并发会导致响应时间显著升高
但是,这里需要做两点说明:
- 多个接口的限流可以通过redis集群分片存储的方式提高性能;
- 本测试场景,没有thinking time和网络latency,所以在真实场景中,对并发的支持会更好些。
- 测试代码与Redis部署在同一台机器上
- Redis连接池设置等于并发线程数
- Redis数据不落硬盘
- 并发数=12,此时根据上面的场景一的测试,并未出现CPU瓶颈
- 接口数不同,接口请求随机分散
- 每个请求之间无thinking time
- 事先10万次限流请求预热JVM
- 测试Redis计数器并发请求下的性能表现
原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中U=N表示接口数目;%9999表示99.99%响应时间;时间单位为毫秒。
多个接口并发限流并没有提高tps和响应时间,同时也应征了:Redis执行命令是单线程工作模式。
- 测试代码与Redis部署在同一台机器上
- Redis连接池设置等于并发线程数
- Redis数据不落硬盘
- 并发数=100
- 接口数=100,接口请求随机分散。
- Thinking time从1ms开始逐渐增加
- 事先10万次限流请求预热JVM
- 引入thinking time更加接近真实的使用场景,关注引入thinking time,在相同的高并发下,是否响应时间大大减少?
原始数据,备注:仅仅摘录了一组有代表性的测试结果,其他组测试结果限于篇幅未罗列。 其中D=N表示thinking time;%9999表示99.99%响应时间;时间单位均为毫秒。
为了更加直观,做了个图:
随着thinking time的加大,高并发下响应时间逐渐递减。因为分布式限流的瓶颈主要来自于Redis,随着thinking time的加大,并发线程的请求频率减少,对redis的压力减小,排队等待执行的请求减少,所以整体的响应时间减少!也应征了我们前面说的,真实的线上场景,在可接受的响应时间内,分布式限流可以支持更高的并发。
限流规则包含1万个接口,执行100万次限流请求,请求都是限流规则中配置的接口,然后执行heap dump,通过工具(如MAT)分析规则占用的内存,通过Redis本身的info命令统计内存的占用。
- 1万条限流规则在内存中只占用了2.5MB左右的空间。
- 针对这1万个接口的100万次请求中峰值占用约1MB空间。
所以对内存和Redis的消耗完全可以忽略。
100线程并发请求,每个请求的thinking time=5ms, 整个的请求压力大约在2万次/秒,持续运行半个小时,看JVM内存等各个资源消耗情况是否合理平稳。
- 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