原创文章,转载请注明出处:http://my.oschina.net/feichexia/blog/477523
总原则
-
无论是对HTTP接口、Thrift接口或者Web Service接口进行压测,发起测试的机器都要尽可能靠近被压测的应用服务器,这样才能得到更准确的压测结果(有效减少网络传输开销带来的干扰)
-
排除非业务因素影响:压测前先确认硬件(CPU、内存、IO、网络等)、被压测接口用到的中间件(Tomcat、Nginx、Redis、RabbitMQ等)的配置信息和健康状况。如果存在硬件故障或者中间件故障,则需要先排除掉
-
确定压测目标,比如是压测tps、内存消耗量还是其他如要能支持多少并发用户,达到多少tps
-
确定一个压测基准,比如压测某个HTTP API接口的tps时,可以先压测心跳检测接口(业务逻辑最少),比如压测得到5000tps,那么就可以断定其他业务逻辑更复杂的HTTP API接口的极限tps不可能超过5000tps,无论你怎么优化(这里的优化特指业务性能优化,如果中间件优化了,则需要重新确定压测基准)
-
压测前最好用ping、traceroute查看下网络连通状况
-
一般先对单台服务器做压测,单台服务器压测达到期望的压测指标后再扩展到集群环境对多台服务器做压测
-
压测服务器尽可能和线上服务器一致或者类似,确认发起测试的服务器本身不存在性能瓶颈(比如还没开始压测,发起测试的机器CPU就已经100%、测试机器在创建多一些线程的时候CPU增长很快)
-
尽可能复制或者模拟真实流量来做压测。比如在压测HTTP API接口时,如果后端做了Cache,一般第一次请求会Cache Miss,但后续请求都会Cache Hit,所以重复请求同一个url来压测可能会得到错误的“乐观结果”。推荐用tcpcopy来复制线上真实流量(tcpcopy)
-
压测时先用少量并发用户、少量总请求数,然后逐渐增大并发用户数、总请求数。同时查看系统业务日志、系统负载情况(top)、系统资源消耗变化情况(vmstat)、GC情况和出错率等
-
压测时间尽可能越长越好,以便观察系统在一段较长时间内的稳定性(以前遇到过系统固定跑一天就崩溃的情况)
-
压测HTTP接口时,需要确认Nginx是否开启gzip压缩(如果响应数据包挺大时开启gzip压缩与否对结果影响挺大)、KeepAlive开启状态、HTTP Cache开启状态等
-
压测时获取到的应用内部运行信息越详细越好,比如Tomcat连接池、Thrift客户端连接池、Thfift服务端连接池的运行状态,一个请求各环节的执行时间(日志埋点)
-
压测前需要想清楚整个系统从前到后的拓扑结构图,可以在纸上画下来
-
如果应用本身每次请求都会调用第三方服务接口,那么先对第三方服务接口做压测,先确认第三方服务接口不存在性能问题
压测流程
-
检查硬件和中间件配置信息和运行健康状况,比如Tomcat最大连接数设置是否合理、Nginx配置是否合理等,确保它们不会影响压测结果
-
确定压测目标,比如“压测某HTTP接口的极限,要求1000并发用户下单台tomcat能达到5000tps”
-
部署应用到压测服务器,准备好发起测试的服务器(如果能和压测服务器一样最好,不能一样至少也要做到在同一个局域网内),然后用ping、traceroute查看下网络连通状况
-
确定压测基准,比如获取心跳检测请求的极限tps
-
构造尽可能接近真实用户访问情况的测试数据,比如用tcpcopy复制线上真实流量
-
逐渐增大并发量和总请求数,观察系统层面的各项 指标和应用运行状况,采用的工具一般是ab、jmeter、loadrunner等
-
有时候可能需要长时间让系统高负载运行,比如观察系统长时间处于高负载情况下是否会出现GC故障
一些经验教训
-
不要迷信“推荐配置”,需要根据实际情况调整配置。比如现在我们Thrift服务的selector线程数推荐值为2,worker线程数推荐值为10,然而对平均执行时间10ms左右的Thrift接口做压测时发现大量请求被阻塞,而当调整selector线程数为20、worker线程数为100后发现阻塞情况显著减轻
-
压测时一切判断必须基于真实测量数据,不能想当然。比如认为某个环节的逻辑很少,然后武断地认为性能瓶颈肯定不会出在那个环节,然后就忽略测量这个环节的耗时
-
可能不止一个环节存在性能瓶颈,比如某个请求会流经A => B => C => D四个环节,经过测量发现当前性能瓶颈存在于B环节(比如80%的时间都消耗在B环节),然后经过努力终于把环节B的耗时降下来了,也许你会发现吞吐量和平均响应时间还是上不去并且再测量后发现现在性能瓶颈又变成C了。原因是:实际上B和C两个环节都存在性能问题,只不过未对B环节调优之前,大部分请求流量都堵塞在B环节,导致到达C环节的请求流量压力非常小,所以C环节的性能问题也就凸显不出来了;而当排除B环节的性能问题后,C环节单位时间内接收到的流量剧增,自然而然C环节的性能问题就暴露出来了。也正因此,对于复杂的业务流程,压力测试很难做到一蹴而就,需要逐一排查突破
-
尽可能自动化获取各个环节的性能数据。如果能通过一个集成的监控系统直观地观察到各个环节的耗时,那么对于诊断整个系统的性能瓶颈将非常有帮助(当你尝试过手动插入执行时间记录日志并收集分析后就能切身体会这点了)。同时监控系统也将为提升系统稳定性和可用性提供一大保障
-
即使你依赖的一个服务号称能达到tps多少多少,号称性能有多快多快,也有必要先弄清楚它的压测场景(用多少数据测的、并发用户多少、总请求多少、测试请求数据是什么样的、能否复现)。确认没问题后还要再检查自己的压测设置有没有问题,比如一个高性能HTTP接口也可能因为你的网络环境或者HTTP客户端连接池大小设置不当、超时时间设置不当等原因而测不出你预期的性能
-
高并发场景如果使用log4j(假如采用默认配置)打印大量日志,会对系统吞吐量造成巨大影响,因为默认log4j写日志是同步写,多线程并发写日志时会等待一个同步锁,如下:
/*
* org.apache.log4j.Category.callAppenders
*/
public void callAppenders(LoggingEvent event) {
int writes = 0;
for(Category c = this; c != null; c=c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized(c) {
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
推荐使用logback替换log4j,或者采用异步写日志并调大log buffer size