任何一个严肃的软件或者网站都应该有明确的性能需求,这个需求在软件设计之初就需要考虑,因而成为了影响软件或者网站架构的一个重要因素。从这个角度来看,性能/可扩展性这样的非功能需求其实比很多功能需求更加重要。为了明确满足性能需求,性能测试自然是必不可少的一环,把这个环节做成熟,是我最近一直在学习考虑的事情。
然而这件事情不简单,Herry H. Liu 在他的 Software Performance and Scalability: A Quantitative Approach 一书中写了这么一段话:
It is generally agreed in the software community that it takes at least five years for a software engineer to become proficient in testing, optimizing, and tuning the performance and scalability of a software system.
且不说这件做好这样的事情是否真需要积累五年那么长时间,就我最近的一些实践来看,光测试一个简单的 HTTP 服务并看懂并理解各项指标就不是件容易的事情。例如说,我们通常关心的一个网站性能指标是响应时间(Response Time),即我发出一个请求到我收到完整返回的时间,以下是我一个测试的结果报告:
当并发的请求数到达某个数值的时候(图中大概为160),响应时间直接上升并产生了相当比例的失败返回。这大概表明了系统的压力极限,那好,下一步要分析的是,这个压力极限背后的瓶颈是什么?哪一块资源不够用了?这就好比,当一个人跑步跑了一个小时之后,跑不动了,原因是什么?缺水?缺糖?腿部肌肉锻炼不够?心肺功能跟不上?脚拐到了?原因多种多样……
对于一个软件系统来说,需要分析的地方大概有:CPU、内存、磁盘I/O、网络I/O、外部系统依赖;如果是Java应用内部的性能问题,还要分析JVM的GC、线程数等等;此外,还有数据库性能,或者其他我想不到的…… 可以看到,这其中的每个点,都是一个很大的主题、都涉及很多理论、很多工具,当所有这些融合在一起工作,找出其中出问题的地方显然不是件容易的事情。这和上医院看病很像,一个表面看起来很简单的感冒,搞大了医生会验血、验尿、做B超甚至CT,道理是一样的,无非就是收集全面的数据并基于此得出更准确的判断。
提到性能测试就必须提一下系统的可扩展性,单独测试单个用户请求的响应时间意义是有限的,除了响应时间我们还要关心吞吐量(Throughput),即单位时间内系统能处理的请求数量,一个常见的吞吐量指标就是TPS(Transaction Per Second),每秒完成的交易数。需要注意的是TPS这样的数字和并发数是不一样的,一秒内可能完成了1000个交易,但同一时间的并行的交易可能只有200(前0.2秒200并行,完成后,再来200并行,以此类推)。总之我们希望,在响应时间位于可接收的范围内这一前提下,系统的吞吐量尽可能大。至于可扩展性,通常指通过水平扩展,指的是能够通过水平增加硬件(单台机器2G内存变8G是垂直扩展,搞4台2G机器是水平扩展)提高系统吞吐量。当然,如果不清楚系统的资源瓶颈,扩展也就无从谈起。另外,如果系统架构天生不支持水平扩展(例如,把用户状态缓存到应用程序内存中,应用程序扩展到多台机器后,这些状态咋办?),那就死翘翘了。
我相信,有基本素养的开发人员/测试人员,都会手动做一些性能测试,例如用简单的 Apache ab 看下系统性能。然而,把性能测试正式地集成到开发流程中,不是件容易的事情。ThoughtWorks Radar 所提倡的 Performance testing as a first-class citizen 说的大概也就是这个事情。
如果能做到这一点,好处是显而易见的,你几乎每天都能很方便的得到系统性能报告,当代码变更导致系统性能下降的时候,能快速得到反馈,能大大降低性能瓶颈排查的成本。当你对系统进行性能调优的时候,也能得到及时的反馈。然而,为了做到这一点,你至少需要:
1. 尽量模拟真实线上的环境
2. 全自动、快速地持续部署
4. 量化的指标以实现自动验证
这几条要求团队拥有非常全面的技能,能把握性能需求,能设计可靠的测试用例、能分析量化的指标、能持续部署系统……你感受下,看看自己团队是否有这个能力。
软件性能和可扩展性是个非常有意思也非常有挑战的主题,以上是我首次真正去学习实践的小小总结,我会进一步学习思考,记录心得。