性能问题分析和调优
性能优化是应用程序开发很重要的环节,也是比较复杂的过程。分析性能问题的关键是能够收集到尽可能多的信息,识别性能瓶颈。 Java Chassis针对性能分析提供的最有用工具是应用性能监控。应用性能监控 默认周期性收集系统性能数据,并将数据输出到日志文件。应用性能监控数据统计提供了非常高效的实现,建议应用程序默认打开。
应用性能监控推荐下面的配置:
servicecomb:
metrics:
window_time: 60000
invocation:
latencyDistribution: 0,1,10,100,1000
Consumer.invocation.slow:
enabled: true
msTime: 1000
Provider.invocation.slow:
enabled: true
msTime: 1000
publisher.defaultLog:
enabled: true
endpoints.client.detail.enabled: true
结合业务自己的日志系统,可以将性能统计日志存储到独立的日志文件,减少对业务日志查看的干扰。
关键处理环节
在 RPC
请求的处理过程中,有些处理环节非常重要,多数性能瓶颈都和这些环节有关。Java Chassis
核心处理环节可以简单概述为:
- Provider流程 : 接收请求 -> 创建 Producer Invocation -> 执行处理链 -> 执行业务逻辑 -> 执行处理链 -> 发送响应
- Consumer流程 : 创建 Producer Invocation -> 执行处理链 -> 发送请求 -> 等待响应 -> 执行处理链
在Provider流程
中,存在线程池资源,线程池竞争的时间通过 queue
来表示。 在 Consumer流程
中,存在连接池资源,连接池竞争的时间通过 connection
来表示。 网路传输的时间在 metrics
里面未体现,一些性能优化问题,还需要结合平时的性能测试经验数据,来判断是否存在性能问题,比如,Consumer流程
的wait
表示等待响应时间,如果从Provider
的 metrics 日志中,查询到总的处理时间为 100ms, wait
时间可能为 200ms, 中间的100ms时间是 metrics 统计不到的时间,这些时间通常用于网络资源的竞争,由操作系统等底层处理程序管理。
需要详细了解 Java Chassis的处理环节,可以通过下载源码,查看 InvocationStageTrace
的各个方法被调用的情况。 通过此方法,能够快速了解 Java Chassis的代码结构和各个 metrics 环节的详细含义。
时延波动
任何处理环节的处理时间都可能存在波动,即使这个环节的处理逻辑非常简单,比如业务逻辑中只包含了一段 a+b
的计算,并返回结果。 在 Java 应用程序中,造成波动的因素大概有如下几种情况:
- 并发请求。并发请求会竞争CPU资源,请求越多,逻辑被中断和延迟处理的概率就会增多,表现为整体处理时间变长。
- 垃圾回收。Java 执行垃圾回收会中断处理过程,也会表现为逻辑处理变长。
因此,当识别到 metrics
的某个非常简单的处理环节(没有IO、没有锁)的时延变长的时候,这通常是系统繁忙的一种表现,在分析性能瓶颈的过程中, 需要收集 CPU、内存使用率、TPS、垃圾回收时间等数据,来进一步判断触发性能问题的原因。
初始化过程的时延
应用程序接收的第一个请求,通常时延比较高,这是因为很多预处理过程需要在第一次请求的初始化。包括:加载服务元数据信息、初始化线程池、连接池、建立连接,以及业务逻辑的初始化数据库等操作。这些初始化操作通常都会使用锁,如果是并发请求,那么每个并发请求的处理时间都会变长。
在应用程序运行过程中,也可能出现类似的初始化现象。比如系统在晚间很久没有用户使用,上班时间突然大量用户并发登录。 系统未使用期间,有些资源会被释放,比如线程池、连接池,用户会话信息等。
初始化过程会影响到系统毛刺,初始化过程耗时,会影响到系统的错误率。特别是设置非常短的请求超时时间的条件下。
其他重要环节的时延
- 线程池排队(queue) : 提供者一般会通过线程池处理业务逻辑。如果线程池出现排队请求,即使排队请求很少,也意味着当前的请求频率超过了系统处理能力。首先可以查看CPU使用率,如果使用率高,这种情况就只能优化指令,或者提升处理能力;如果CPU使用率低,可以结合堆栈信息,进一步分析导致排队的具体逻辑是等待IO还是由于锁、资源竞争导致的。
- 获取连接池(connection): 建立连接和连接池竞争是
RPC
常见的性能瓶颈。 建立连接是比较耗时的操作,以HTTP
为例, 建立一次连接通常需要100ms
左右的时间,服务端通常只能处理数百的并发连接。 如果并发过大,就会导致连接超时。 涉及连接耗时的情况,需要通过连接池设置,分析适合运行环境需要的最大连接数,进行合理的连接规划。
性能问题分析
综合上面的一些场景,可以发下性能问题分析不是简单的看某条耗时数据就可以进行准确的分析,这些数据通常只是给一个指示,用于判断性能分析的基本方向,然后结合方向,收集更多的信息去印证。 性能问题一般以超时的方式表现出来,当出现性能问题的时候,建议收集如下日志:
- consumer调用出错日志(如果有的话,比如超时);
- provider调用出错日志(如果有的话,比如丢弃请求);
- consumer对应时间段的metrics日志,包括周期统计数据和慢调用日志;
- provider对应时间段的metrics日志,包括周期统计数据和慢调用日志;
通常结合上面的日志,就能够初步识别出性能瓶颈的位置了。metrics日志重点反映的是Java Chassis各个处理环节的耗时, 对于线程排队的场景,需要进一步识别业务的性能瓶颈:
- 通过
jstack
采集堆栈信息。这个通常比较难于收集,需要在出现性能缓慢的时刻,抓取。建议每隔几秒钟,连续抓取3个以上堆栈,用于分析。 - 对于涉及内存管理和垃圾回收的问题,需要收集GC的信息,和内存增长趋势。
收集性能数据是分析的第一步,理解性能数据需要熟悉Java Chassis的处理过程、线程池排队和执行原理、JVM线程堆栈和GC机制等,这里不详细 描述,结合实际问题在过程中学习是更好的提升方式。 Java Chassis中通过performance标签的问题和性能有关,可以作为业务性能问题 分析的参考,碰到性能问题,也可以提交issue,找社区寻求帮助,记得在issue中包含metrics信息。
性能常识
下面提供一些常见的数据,帮助识别性能瓶颈。这些数据并不是理论上精确的,仅供参考。
- 通过一次调用,可以收集到一个请求发出到收到响应的时延,这个时延称为平均时延。并发场景,平均时延并不是固定的,通常随着并发数 增大而增大。
- 一个请求的平均时延在0.1ms~1ms之间,TPS可以达到1万~10万。 0.1ms的时延,是不带任何业务逻辑的开发框架时延,当平均时延 小于1ms的情况,进一步提升性能,需要考虑框架性能调优,还会涉及操作系统、网络等调优,比较复杂;大于1ms的情况,通常都需要 调优业务代码,框架不是性能瓶颈。
- 一个请求的平均时延在1ms~10ms之间,TPS可以达到1千~1万。
- 一个请求的平均时延在10ms~100ms之间,TPS可以达到1百~1千。
- 有个简单的公式,可以估算最大TPS:
CPU核数 * (1000/平均时延)
< TPS <线程数 * (1000/平均时延)
。 越是计算密集型的 任务,TPS越接近CPU核数 * (1000/平均时延)
;空闲等待任务越多,越是接近线程数 * (1000/平均时延)
。 压测的时候,如果并发 请求大于上述估算值,那么就会出现大量请求超时。