服务性能记录

作者: | 更新日期:

最近做一个项目, 需要评估一下性能与瓶颈所在之处, 这里简单记录一下.

本文首发于公众号:天空的代码世界,微信号:tiankonguse

前言

随着业务的快速成长,访问量越来越高,除了对功能要求很高以外,对性能要求也越来越高。 在实际工作中,我们往往会被一些问题所困扰。

  1. 线上服务容量是多少?性能痛点在哪里? 可伸缩性和可靠性怎样? 预先知道了系统的容量,做到心中有数,才能为最终规划整个运行环境的配置提供有利的依据。

  2. 新开发的功能是否满足性能指标? 重新修改的代码会不会带来性能问题? 对服务或工具的参数修改是否有效果?
    如果在上线用前就能进行验证,那么不仅能极大降低部署时发生意外的概率,还能为性能优化提供指导。

现状

为尝试解决上述问题,我们在多个项目上进行过性能测试,使用过的方法主要分成三类。

方案 具体方式 优点 缺点
人为模拟请求 自己写代码或者使用简单的工具如httpload等去模拟用户请求进行测试 操作简单,能快速的得到cpu、mem、 load、 qps等极限值。 缺少真实用户交互行为,缺乏真实性
copy线上流量 使用tcpcopy工具实时copy线上流量到某台机器 操作简单,是真实线上请求,且对线上服务压力无影响 需要准备一套跟线上机器配置、依赖一致的独立环境,同时如果是服用线上的环境的话,一些写操作的请求被copy会有问题
线上流量切换 直接用线上的机器和环境,通过调整nginx配置参数,逐渐将要做压测的机器的权重增加,然后观察该机器各个指标性能 真实生产线流量,能把用户行为导向压测服务器,是最为真实的用户行为,能够把一些需要登陆,有用户交互行为的性能真实的反映出来 因为是用生产系统真实流量来模拟压测,无法得出最大值,如果阀值设置有误,也存在一定的风险。此外该性能测试也不能经常进行

性能测试指标

不同服务测试指标应该不同,相应的标准也不同,例如接入层服务和后端服务指标是不同的。
如果我们能为各个服务制定类似如下的标准,以后再进行性能测试就有了参考依据。
随着服务的发展,这些标准也会随之相应改动,要求会越来越严格。

判断指标 不通过的标准
超时概率 大于万分之一
错误概率 大于万分之一
平均响应时间 超过100ms
每分钟处理的请求量 小于2w
cpu使用率 平均每核超过75%
负载(load) 平均每核超过1.5

性能测试

根据压力变化模型,将性能测试分成狭义的4种类型:

  • 性能测试:a点到b点之间的系统性能
    狭义的性能测试,是指以性能预期目标为前提,对系统不断施加压力,验证系统在资源可接受范围内,是否能达到性能预期。
  • 负载测试 :b点的系统性能
    狭义的负载测试,是指对系统不断地增加压力或增加一定压力下的持续时间,直到系统的某项或多项性能指标达到极限,例如某种资源已经达到饱和状态等。
  • 压力测试:b点到d点之间
    狭义的压力测试,是指超过安全负载的情况下,对系统不断施加压力,是通过确定一个系统的瓶颈或不能接收用户请求的性能点,来获得系统能提供的最大服务级别的测试
  • 稳定性测试:a点到b点之间
    狭义的稳定性测试,是指被测试系统在特定硬件、软件、网络环境条件下,给系统加载一定业务压力,使系统运行一段较长时间,以此检测系统是否稳定,一般稳定性测试时间为n*12小时

注意事项

  • 发起测试的机器都要尽可能靠近被压测的应用服务器,这样才能得到更准确的压测结果(有效减少网络传输开销带来的干扰)
  • 排除非业务因素影响:压测前先确认硬件(CPU、内存、IO、网络等)、被压测接口用到的中间件(Tomcat、Nginx、Redis、RabbitMQ等)的配置信息和健康状况。如果存在硬件故障或者中间件故障,则需要先排除掉
  • 确定压测目标,比如是压测tps、内存消耗量还是其他,要能支持多少并发用户,达到多少tps
  • 确定一个压测基准,比如压测某个HTTP API接口的tps时,可以先压测心跳检测ping接口(业务逻辑最少),比如压测得到5000tps,那么就可以断定其他业务逻辑更复杂的HTTP API接口的极限tps不可能超过5000tps,无论你怎么优化(这里的优化特指业务性能优化,如果中间件优化了,则需要重新确定压测基准)
  • 压测前最好用ping、traceroute查看下网络连通状况
  • 一般先对单台服务器做压测,单台服务器压测达到期望的压测指标后再扩展到集群环境对多台服务器做压测
  • 压测服务器尽可能和线上服务器一致或者类似,确认发起测试的服务器本身不存在性能瓶颈(比如还没开始压测,发起测试的机器CPU就已经100%、测试机器在创建多一些线程的时候CPU增长很快)
  • 尽可能复制或者模拟真实流量来做压测。比如在压测HTTP API接口时,如果后端做了Cache,一般第一次请求会Cache Miss,但后续请求都会Cache Hit,所以重复请求同一个url来压测可能会得到错误的“乐观结果”。推荐用tcpcopy来复制线上真实流量
  • 压测时先用少量并发用户、少量总请求数,然后逐渐增大并发用户数、总请求数。同时查看系统业务日志、系统负载情况(top)、系统资源消耗变化情况(vmstat)、GC情况和出错率等
  • 压测时间尽可能越长越好,以便观察系统在一段较长时间内的稳定性(以前遇到过系统固定跑一天就崩溃的情况)
  • 压测HTTP接口时,需要确认Nginx是否开启gzip压缩(如果响应数据包挺大时开启gzip压缩与否对结果影响挺大)、KeepAlive开启状态、HTTP Cache开启状态等
  • 压测时获取到的应用内部运行信息越详细越好,比如Tomcat连接池、Thrift客户端连接池、Thfift服务端连接池的运行状态,一个请求各环节的执行时间(日志埋点)
  • 压测前需要想清楚整个系统从前到后的拓扑结构图,可以在纸上画下来
  • 如果应用本身每次请求都会调用第三方服务接口,那么先对第三方服务接口做压测,先确认第三方服务接口不存在性能问题

压测流程

  1. 检查硬件和中间件配置信息和运行健康状况,比如Tomcat最大连接数设置是否合理、Nginx配置是否合理等,确保它们不会影响压测结果
  2. 确定压测目标,比如“压测某HTTP接口的极限,要求1000并发用户下单台tomcat能达到5000tps”
  3. 部署应用到压测服务器,准备好发起测试的服务器(如果能和压测服务器一样最好,不能一样至少也要做到在同一个局域网内),然后用ping、traceroute查看下网络连通状况
  4. 确定压测基准,比如获取心跳检测请求的极限tps
  5. 构造尽可能接近真实用户访问情况的测试数据,比如用tcpcopy复制线上真实流量
  6. 逐渐增大并发量和总请求数,观察系统层面的各项 指标和应用运行状况,采用的工具一般是ab、jmeter、loadrunner等
  7. 有时候可能需要长时间让系统高负载运行,比如观察系统长时间处于高负载情况下是否会出现GC故障

一些经验教训

  1. 不要迷信“推荐配置”,需要根据实际情况调整配置。比如现在我们Thrift服务的selector线程数推荐值为2,worker线程数推荐值为10,然而对平均执行时间10ms左右的Thrift接口做压测时发现大量请求被阻塞,而当调整selector线程数为20、worker线程数为100后发现阻塞情况显著减轻

  2. 压测时一切判断必须基于真实测量数据,不能想当然。比如认为某个环节的逻辑很少,然后武断地认为性能瓶颈肯定不会出在那个环节,然后就忽略测量这个环节的耗时

  3. 可能不止一个环节存在性能瓶颈,比如某个请求会流经 A => B => C => D四个环节,经过测量发现当前性能瓶颈存在于A环节(比如80%的时间都消耗在A环节),然后经过努力终于把环节A的耗时降下来了,也许你会发现吞吐量和平均响应时间还是上不去并且再测量后发现现在性能瓶颈又变成C了。原因是:实际上B和C两个环节都存在性能问题,只不过未对B环节调优之前,大部分请求流量都堵塞在B环节,导致到达C环节的请求流量压力非常小,所以C环节的性能问题也就凸显不出来了;而当排除B环节的性能问题后,C环节单位时间内接收到的流量剧增,自然而然C环节的性能问题就暴露出来了。也正因此,对于复杂的业务流程,压力测试很难做到一蹴而就,需要逐一排查突破

  4. 尽可能自动化获取各个环节的性能数据。如果能通过一个集成的监控系统直观地观察到各个环节的耗时,那么对于诊断整个系统的性能瓶颈将非常有帮助(当你尝试过手动插入执行时间记录日志并收集分析后就能切身体会这点了)。同时监控系统也将为提升系统稳定性和可用性提供一大保障

  5. 即使你依赖的一个服务号称能达到tps多少多少,号称性能有多快多快,也有必要先弄清楚它的压测场景(用多少数据测的、并发用户多少、总请求多少、测试请求数据是什么样的、能否复现)。确认没问题后还要再检查自己的压测设置有没有问题,比如一个高性能HTTP接口也可能因为你的网络环境或者HTTP客户端连接池大小设置不当、超时时间设置不当等原因而达不到你想要的性能

  6. 高并发场景如果使用log4j(假如采用默认配置)打印大量日志,会对系统吞吐量造成巨大影响,因为默认log4j写日志是同步写,多线程并发写日志时会等待一个同步锁. 推荐使用logback替换log4j,或者采用异步写日志并调大log buffer size.

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越