性能压测是当服务上线前,或者之后重要需求发布流程中,需要做的必要测试;以模拟真实流量的方式,获取当前系统的性能指标、是否存着高并发隐患、瓶颈等信息的手段。
性能压测处于什么位置?或者说什么时候去做呢?如何做?
如何做好线上环境的性能压测、全链路压测,如何做到压测结果准确无误,不影响外部环境、不污染数据,需要深入思考。
一、测试分类
按照上线流程:
单元测试
集成测试
兼容性测试(前端、客户端)
性能测试
运维阶段:
安全测试
容灾测试(故障恢复测试)
由上可以看出,性能测试是应用服务在上线过程中的一个流程,除非是内部系统,访问量少,否则都应当做一下性能测试。
安全测试又叫渗透测试,是运维和研发需要关注的问题;尤其是对数据安全要求高的系统。当一个服务稳定运行后,模拟一些攻击手段,防止SQL注入、网络攻击、脚本攻击等。
容灾测试是容灾方式的验证,容灾方式包括:双机房建设、异地多活、两地三中心。
二、压测目的
了解吞吐量
瓶颈值
系统隐患
可用:一次请求,到达服务器,有回应;那么针对这次请求,服务是可用的,无论返回成功或者失败。
不可用是指请求没有返回,长时间等待,没有应答,客户端不知道结果。
高可用级别:99%、99.9%、99.99%、99.999%
大家经常说的系统可用性达到三个9,就是指99.9%。
高可靠:是指服务可靠,数据可靠。请求返回是成功,数据写入正确,分布式环境下数据一致性等。
三、压测指标
四、实现流程
1. 压测工具选型
Jmeter + Influxdb + Grafana + Collectd
2.压测环境准备
服务环境
压测机
数据库
Redis
MQ
3.压测数据准备
日活数据:数据要真实、流量真实,结果才能越接近线上真实性能指标。
数据隔离:防止污染真实用户数据
数据恢复
热点数据
压测预热:尽量模拟线上真实环境情况,如缓存命中率
4.压测开发设计
挡板
Mock数据
流量识别
压测场景
加压策略
挡板:是用来拦截调用第三方的请求,防止压测对第三方造成影响;提前设计好挡板逻辑,识别压测流量,执行挡板逻辑,并且为了真实,还可以合理延迟一定时间再返回Mock数据。
流量识别:nginx做负载均衡,upstream 转发服务器ip 时,可以获取http请求Header的参数,我们可以给压测流量header加标记参数,nginx获取参数判断后,转发压测流量到压测机器。
5.监控
监控很重要,可以说是压测中最为重要的地方,如果没有监控,或者监控不准确,会严重影响压测本身准确性。监控数据不详细,也会影响性能调优。
一般我们需要监控的如下:
CPU、内存
IO:网络、磁盘
Http请求、接口方法
JVM
中间件
数据库负载
带宽占用
其中对接口和方法的监控需要有,而且要准确,是定位性能问题的有效手段,越细化越好。
以下是一些监控工具:
通过上图可以直观的看出JVM堆内存的增长情况和GC回收频率、回收后内存大小。如果回收后的内存在持续增高,且当前请求没有增长,说明有内存泄露,具体什么对象泄漏,需要通过jmap等命令,查看堆内存heap的详情。
对比GC前后的对象数量,分析是哪些没有回收。
五、性能优化
注意:
慎重优化,谨记二八原则 和 Amdahl定律。
优化一定要伴随着压测,通过性能指标、数据对比来论证和调整,否则没有意义。
1.优化目标
提升吞吐量
降低 RT
2.优化方向
硬件层面:CPU、内存扩容、换SSD固态硬盘
软件层面:集群扩容、代码优化、存储优化(缓存、数据库)、减少IO
3.优化方式
代码优化:请求合并、异步、Cache、池化
JVM调优
Linux调优
JVM调优
1. 调优关注点
减少JVM不可用时间:STW(Stop the word)
GC 频率
安全点SafePoint
安全点是需要注意的问题,很多初学者会忽略安全点线程等待时间,其实STW时间包括安全点等待时间。
2. 常用GC回收器
ParNew + CMS (低延迟、低核、小内存场景)
G1 (高吞吐量、高核、大内存)
G1只有在多核CPU,大内存情况下,才能体现出它的优势,如果内存过小,表现反而不如ParNew+CMS。
那么到底多大算大内存呢?
根据R大给出的建议是:以堆内存8g为限,大于8g用G1,小于8g用CMS。
3. JVM参数分类
-:标准参数,所有JVM都必须实现,且向后兼容。
-X :非标准参数,默认JVM实现该参数,不保证向后兼容。
-XX:非stable参数,不同JVM有所不同,将来可能会取消。
4. G1关键参数
-Xms16g -Xmx16g
-XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:InitiatingHeapOccupancyPercent=60
-verbose:gc -Xloggc:logs/gc_%p.log -XX:+DisableExplicitGC
-XX:-UseLargePages -XX:+PrintGCApplicationStoppedTime -XX:-OmitStackTraceInFastThrow
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
需要注意的是,G1最好不要加新生代大小参数 -Xmn ,如果固定了新生代大小,G1就没法根据 -XX:MaxGCPauseMillis 设定的最大期望GC时间,动态调整堆内存的新生代和老年代大小,相当于-XX:MaxGCPauseMillis 失效了。除非你很熟悉G1原理,加了新生代大小,配合其他参数,也可以调优出一个不错的结果。否则还是交给G1本身的自动调整吧。
5. GC 日志
2020-01-09T11:05:34.836+0800: [GC pause (G1 Evacuation Pause) (young) ……
[Eden: 8576.0M(8576.0M)->0.0B(8576.0M) Survivors: 16.0M->16.0M Heap: 8643.7M(14.0G)->68.2M(14.0G)]
[Times: user=0.05 sys=0.04, real=0.02 secs]
2020-01-09T11:05:34.863+0800: 2043140.818: Total time for which application threads were stopped:0.0286394 seconds, Stopping threads took: 0.0002674 seconds
2020-01-09T12:01:10.488+0800: 2046476.443: Application time: 3335.6250688 seconds
2020-01-09T12:01:10.490+0800: 2046476.444: [GC pause (G1 Evacuation Pause) (young) 2046476.444:
gc日志中 Stopping threads took 就是安全点时,线程的等待时间,所以STW时间应该是:GC时间 + 安全点等待时间。
如果 Stopping threads took 较大,说明代码程序是有问题的,需要深入查看;
具体什么情况会导致安全点时间过长,请参考:
HBase实战:记一次Safepoint导致长时间STW的踩坑之旅
Linux调优
我在对业务项目、RocketMQ、Redis、codis集群、Elasticsearch、Hbase等进行优化过程中,经常遇到一些相同的问题,我总结了下,主要有以下:
慎用交换区swap
优化方式:禁止swapping 或者设置 swappiness = 1
swapping会导致gc过程从毫秒级变成分钟级
善用 /dev/shm/
/dev/shm/ 是linux的文件内存系统,是共享内存,直接从物理内存中开辟,根据使用情况会动态扩容。
针对一些写入文件,IO性能要求高的场景,可以使用/dev/shm/,例如gc日志存放。
高并发场景下,如果打开安全点日志,安全点日志是在安全点内打印,是同步的,所以日志写入文件速度,会严重影响gc快慢;应当将gc日志配置改为:
-Xloggc:/dev/shm/gc_%p.log
其他linux调优,一般大点的公司,都会有自己的运维团队,运维就会做基本的优化,例如tcp连接优化等,这里只介绍运维可能忽略的,不知道的点,这时候就需要研发同学介入,找运维做好应用相关的Linux内核优化。