响应时间就是用户感受软件系统为其服务所耗费的时间,对于网站系统来说,响应时间就是从点击了一个页面计时开始,到这个页面完全在浏览器里展现计时结束的这一段时间间隔,看起来很简单,但其实在这段响应时间内,软件系统在幕后经过了一系列的处理工作,贯穿了整个系统节点。根据“管辖区域”不同,响应时间可以细分为:
那么客户感受的响应时间其实是等于 客户端响应时间 + 服务器端响应时间 + 网络响应时间。细分的目的是为了方便定位性能瓶颈出现在哪个节点上。
吞吐量是我们常见的一个软件性能指标,对于软件系统来说,“吞”进去的是请求,“吐”出来的是结果,而吞吐量反映的就是软件系统的“饭量”,也就是系统的处理能力,具体说来,就是指软件系统在每单位时间内能处理多少个事务/请求/单位数据等。但它的定义比较灵活,在不同的场景下有不同的诠释,比如数据库的吞吐量指的是单位时间内,不同 SQL 语句的执行数量;而网络的吞吐量指的是单位时间内在网络上传输的数据流量。吞吐量的大小由负载(如用户的数量)或行为方式来决定。举个例子,下载文件比浏览网页需要更高的网络吞吐量。
常见的资源有:CPU占用率、内存使用率、磁盘I/O、网络I/O。
点击数是衡量 Web Server 处理能力的一个很有用的指标。需要明确的是:点击数不是我们通常理解的用户鼠标点击次数,而是按照客户端向 Web Server 发起了多少次 http 请求计算的,一次鼠标可能触发多个 http 请求,这需要结合具体的 Web 系统实现来计算。
并发用户数用来度量服务器并发容量和同步协调能力。在客户端指一批用户同时执行一个操作。并发数反映了软件系统的并发处理能力,和吞吐量不同的是,它大多是占用套接字、句柄等操作系统资源。
另外,度量软件系统的性能指标还有系统恢复时间等,其实凡是用户有关资源和时间的要求都可以被视作性能指标,都可以作为软件系统的度量,而性能测试就是为了验证这些性能指标是否被满足。
在找性能瓶颈之前,我们要先对系统性能有一个概念
如何在不购买新硬件的条件下完成更多的工作?何时才真正需要添加硬件(更多的内存,更快的磁盘、 CPU以及网络接口)?有时只需消除一些简单的瓶颈即可解决许多性能问题——但是要实现它,你必须充分了解自己的计算机和网络,从而找到真正的瓶颈所在。
说得直白一点,系统性能就是在尽可能减少投资的情况下,解决下面两个事:
我们要做优化,就是为了让吞吐量更大,让响应时间更短,在二者之间达到平衡,满足我们的业务要求。
所以,我们要发现性能瓶颈,其实就是找到影响吞吐量和响应时间的地方。
网上的压力测试工具有很多,这里举几个实际工作中常用的
能够在 QA 和实际运行生产环境监测 Java 或 Java EE 应用程序服务器。并以图表的形式显示:Java 内存和 Java CPU 使用情况,用户 Session 数量,JDBC 连接数,和 http 请求、sql 请求、jsp 页面与业务接口方法(EJB3、Spring、Guice)的执行数量,平均执行时间,错误百分比等。图表可以按天,周,月,年或自定义时间段查看。
Apache JMeter 是一个专门为运行和服务器装载测试而设计的、100% 的纯 Java 桌面运行程序。原先它是为 Web/HTTP 测试而设计的,但是它已经扩展以支持各种各样的测试模块。它和用于 HTTP 和 SQL 数据库(使用 JDBC)的模块一起运送。它可以用来测试静止资料库或者活动资料库中的服务器的运行情况,可以用来模拟对服务器或者网络系统加以重负荷以测试它的抵抗力,或者用来分析不同负荷类型下的所有运行情况。它也提供了一个可替换的界面用来定制数据显示,测试同步及测试的创建和执行。
LoadRunner,是一种预测系统行为和性能的负载测试工具。通过以模拟上千万用户实施并发负载及实时性能监测的方式来确认和查找问题,LoadRunner 能够对整个企业架构进行测试。企业使用 LoadRunner 能最大限度地缩短测试时间,优化性能和加速应用系统的发布周期。 LoadRunner 可适用于各种体系架构的自动负载测试,能预测系统行为并评估系统性能。
先去操作系统,操作系统的报告。看看操作系统的 CPU 利用率,看看 内存使用率,看看 操作系统的 IO,还有 网络的 IO,网络链接数,等等。通过了解操作系统的性能,我们才知道性能的问题,比如:带宽不够,内存不够,TCP 缓冲区不够,等等,很多时候,不需要调整程序的,只需要调整一下硬件或操作系统的配置就可以了。说这些是为了提醒你,不要急着去修改你的代码。
如果到了非要动代码的地步,瓶颈这东西也可以根据 2:8 原则来说,20% 的代码耗了你 80% 的性能,找到那 20% 的代码,你就可以优化那 80% 的性能。所以,紧紧锁定那不到 20% 的代码。
常见的互联网架构中,一般都能看到 Spring + MyBatis + MySQL + Redis搭配的身影。一般来说,应用内部的接口都是直接调用的,所谓的面向接口编程,应用间的调用直接调或者通过类似 Dubbo 之类的服务框架来执行,数据格式往往采用 JSON,即统一也方便各数据间做转换和取值,缓存一般使用 Redis 或 Memcached,存储一些对象或 JSON 格式的字符串。对外提供的接口,一般都需要进行压力测试,以便估算其性能,并为后续的调优提供指导方向,以下接口便是在压测过程中出现的各种“奇怪现象”,所谓奇怪,指的是从表象上看与我们正常的逻辑思路不符,但其本质还是我们对压力下程序的表现出来的特征不熟悉,用惯用的知识结构试图去解释,这根本是行不通的。下文是我在一次全面压测过程后对数据进行的分析汇总,其中的现象是很多压测常见的,里面的分析过程及改进措施我认为有很大的参考意义。具体内容如下:(部分接口为了安全我省略了其名称,但不影响我们的分析,另外形如 1N3T 之类的表示的是 1 台 Nginx,3 台 Tomcat,具体的 TPS 数值只是为了说明优化前后的比照,没有实际意义)
每秒钟处理完的事务次数,一般 TPS 是对整个系统来讲的。一个应用系统 1s 能完成多少事务处理,一个事务在分布式处理中,可能会对应多个请求,对于衡量单个接口服务的处理能力,用 QPS 比较多。
每秒钟处理完请求的次数;注意这里是处理完。具体是指发出请求到服务器处理完成功返回结果。可以理解在 Server 中有个 Counter,每处理一个请求加 1,1 秒后 Counter = QPS。
响应时间,处理一次请求所需要的平均处理时间
系统能同时处理的请求数
旧框架,平均响应时间长,应用 CPU 高,程序内部有大量的 BEAN 到 MAP 到 JSON 之间的转换,修改数据库连接数后,TPS 没有提升。
重构系统,用 MyBatis 替代之前的 DAO 操作,减少 BEAN - MAP - JSON 之间的内部数据转换,减少程序内部的无用操作。
TPS 改进后能到 3000 左右,有较大提升,但压测时应用 CPU 几乎跑满,还有改善空间。
单台应用,数据库资源 CPU 都能到 50%,10 台 TOMCAT 在 1万 并发下数据库 CPU 跑满,LOAD 值 700 多,但 DB 的 QPS 也不过 11554,并不算多,因此怀疑是 SQL 执行耗费了 CPU,可能是某条 SQL 没有按索引查找或者索引失效。
查看 SQL 文件发现如下 sql:select count(1) from orders where order_status_id !=40 ,将其改为 select order_id from orders 然后通过程序把 order_status_id != 40 的过滤掉。通过 list.size() 来获取。order_status_id 即使加了索引,由于是 != 比较,所以不会去按索引查找,导致 CPU 高
相同环境下(1台 Nginx,10 台 Tomcat,1000 并发),TPS 由 3000 变成 3727,略有增长,但是 DB 的 CPU 明显下降,变为 30%,效果明显
后端都是 15 台 Tomcat,前端 1 台 Nginx 时 TPS 为 4552,通过 LVS 挂 10 台 Nginx 时为 9608,增长不明显,其 Nginx 和 Tomcat 都压力不大,集群结果不合理,怀疑是 Nginx 转发配置的问题;
未进一步改进:可能是需要调整 Nginx 的参数,之前发现过 Nginx 不同的配置对后端集群环境的 TPS 影响很大
无
旧框架,程序内部有很多 Bean - Map - Json 相互的转换
删除冗余代码、更换连接池包,使用 MyBatis
TPS 由 2000 多增长为 8000 多,DB 的 QPS 为 9000 左右,优化后压测应用的 CPU 占用高,几乎跑满。
1N1T 和 1N10T 的 TPS 一样,都为 5000,增大并发时错误数增多,应用 CPU 耗费 70%,DB 无压力,Nginx 单台通过 ss –s 发现端口占满,即 Nginx 到 Tomcat 之间转发的连接端口 time-wait 状态 6 万多。Nginx 存在瓶颈。
调优 Nginx 参数,将短连接改为长连接
1N3T 的 TPS 能到 17376,Tomat 的 CPU 压力 84%,DB 的 QPS 18000,CPU 69%,应用的资源基本使用到量。
增加应用服务器,TPS 增长不明显,且 Nginx、Tomcat、DB 的负载都不高,说明服务器本身不是瓶颈,考虑是不是网络的问题,通过监控网卡包流量发现网络数据跑满,因为此接口会有大量数据的输出,因此瓶颈在网络上。另外,测试过程中发现 Redis 有报错,Redis 服务器是虚机,可能服务能力有限。
开启 Tomcat 的 gzip 压缩。
同等并发下(1 台 Nginx,10 台 Tomcat,1000 并发),TPS 由 3727 增长到 10022,增长了近 3 倍,效果显著。
经过 Tomcat 的启用 gzip 后,1N10T 下 TPS 为 10022,需进一步提升。
优化 Nginx:
TPS 由 10022 提升为 13270。
1N10T 的 TPS 为 1万3千多,1N5T 的 TPS 为 1万2千多,相差不大,应用的 Tomcat 资源利用没满,CPU 为 65%,DB 的 QPS 已经到 2万多了,单台服务器 DB 基本上到量了,因此再增加应用也没效果,只会导致响应的时间变长。
单台 DB 已经无法改进了,要不提升服务器硬件,要不读写分离。
无
此接口通过 Redis 取数据,TPS 不高才 1000 多,但 CPU 占用了 80%,说明程序内部有大量序列化反序列化的操作,可能是 JSON 序列化的问题。
将 net.sf.json 改成 alibaba 的 fastjson
同等并发条件下 TPS 由 1000 多提升为 5000 多,提高了近5倍。
此接口根据参数从 Redis 中获取数据,每个参数与 Redis 交互一次,当一组参数时 TPS 5133,五组参数时 TPS 1169,多次交互影响了处理性能。
将从 Redis 获取数据的 get 改为 mget,减少交互次数。
五组参数时 1N3T 压测 TPS 9707,据此估算即使是单台 Tomcat,TPS 也能有三四千,性能比单次 get 的调用方式提升了 3,4 倍。
此处说的是可能,因为 Nginx 服务器的 CPU 虽然不高,但 QPS 已经 8000 多,此时应该是 Nginx 的服务器网络流量成为了瓶颈。(只是猜测)
可以增加多台 Nginx 负载,前端加 LVS
1N3T 在 2000 并发下 TPS 为 9849,此时 DB 的 QPS 为 90000,CPU 80%,将 Tomcat 增到 10 台,5000 并发下,TPS 为 7813,DB 的 QPS 为 19000,CPU 75%,load 1,说明压力增大情况下 DB 的压力反而下来了,注意到 Nginx 服务器的网卡流量达到 885M,说明是压力过大情况下,网络满了,发生丢包,导致 DB 端压力反而下来了。
注意压测情况下部分接口由于数据量传输较大,会出现网络瓶颈。
虽然缺少应用的 CPU 及 DB 的 CPU 利用率数据,较低的 TPS 应该是应用的瓶颈,且需要关注是不是 DB 在处理查询的时候缓慢。
TPS 由不到 500 增长为 1300 多
查看是否存在瓶颈资源,可以看到 5 台 Tomcat 集群下,TPS 为 9952,但 DB 的 QPS 为 5-6 万,CPU 利用率为 37%,说明对数据库进行了大量的主键或索引查询,一般单台 DB 的 QPS 也就 4万左右,再增加应用的集群数量,对 TPS 也不会有太大影响。
可以考虑分库
18 台 Nginx,2 Tomcat 时 TPS 8100,此时应用服务器的端口数满,一般来说,Nginx 短连接在高并发下容易导致端口占满的问题。
将 Nginx 改为长连接
TPS 增长为 10733,TPS 稳定,起伏减少,但是 CPU 耗尽。说明 CPU 打满了,此时如果还要优化就的进行代码调优了。
18 台 Nginx,20 台 Tomcat,TPS 才 6842,此时应用 CPU 利用率 85%,说明 CPU 存在瓶颈,但检查此接口并未做大计算量的工作,有可能是日志的级别问题,CPU 在频繁的打日志。
将日志级别由 DEBUG 级改为 INFO 级
同等环境 TPS 由 6842 增长为 23592
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,
咱们下期见!学习更多JAVA知识与技巧,关注与私信博主(666)