作为一个香港服务器用户,你可能会遇到一些 CPU 方面的问题。当我们的数据平台服务器的CPU利用率达到惊人的 98.94%,并长时间保持在 70% 以上时,看起来像是需要扩容的硬件资源瓶颈。然而,仔细考虑后,我们的业务系统并不是高并发或 CPU 密集型应用。利用率过于极端,不应该这么快就达到硬件瓶颈。业务代码逻辑的某个地方一定存在问题。

故障排查步骤

2.1 确定高负载进程的 PID

首先,登录服务器,使用 top 命令确认服务器的具体情况,然后根据情况进行分析判断。

通过观察 load average 和负载评估标准(8核),可以确认服务器存在高负载情况。观察各进程的资源使用情况,可以看到 ID 为 682 的进程 CPU 百分比相对较高。

2.2 确定具体异常业务

这里,我们可以使用 pwdx 命令根据 PID 找到业务进程路径,进而定位责任人和项目:

可以得出,该进程对应数据平台的 web 服务。

2.3 定位异常线程和具体代码行

传统解决方案通常涉及 4 个步骤:

1. top 按 P 排序:1040 // 首先按进程负载排序,找到 maxLoad(pid)
2. top -Hp 进程PID:1073 // 找到相关负载线程 PID
3. printf "0x%x" 线程PID:0x431 // 将线程 PID 转为十六进制,便于后续 jstack 日志搜索
4. jstack 进程PID | vim +/十六进制线程PID - // 如:jstack 1040|vim +/0x431 -

但对于线上问题定位,每一秒都很宝贵,上述 4 个步骤过于繁琐耗时。之前淘宝的 oldratlee 将上述过程封装成一个工具:show-busy-java-threads.sh,可以方便地在线上定位此类问题:

可以得出,系统中一个时间工具类方法的执行 CPU 百分比很高。定位到具体方法后,检查代码逻辑是否存在性能问题。

根因分析

经过前面的分析排查,最终确定了一个时间工具类的问题,导致服务器负载和 CPU 使用率高。

  • 异常方法逻辑:将时间戳转换为对应的具体日期时间格式。
  • 上层调用:计算从当天零点到当前时间的所有秒数,转换为对应格式,并以集合形式返回结果。
  • 逻辑层:对应数据平台实时报表的查询逻辑。实时报表会定时查询,单次查询中存在多次(n 次)方法调用。

可以得出,如果当前时间为上午 10 点,单次查询的计算次数为 10*60*60*n = 36,000*n 次,并且随着时间增加,每次查询的计算次数将随着接近午夜而线性增加。由于实时查询、实时告警等多个模块的大量查询请求需要多次调用该方法,导致大量占用和浪费 CPU 资源。

解决方案

定位到问题后,首先考虑的是减少计算次数,优化异常方法。经过排查,发现在逻辑层,方法返回的集合中的内容并没有被使用,而是简单地使用了集合的大小值。在确认逻辑后,通过新方法(当前秒数 – 零点秒数)简化了计算,替换了被调用的方法,解决了计算次数过多的问题。上线后,观察服务器负载和 CPU 使用率,相比异常时期下降了 30 倍,恢复到正常状态。至此,问题得到解决。

总结

在编码过程中,除了实现业务逻辑外,还需要注意代码的性能优化。能实现的业务需求,和能更高效、更优雅地实现,实际上反映了两种完全不同的工程师能力和境界,后者也是工程师的核心竞争力。

代码写完后,多做 review,思考是否可以用更好的方式实现。在线上问题中不要忽视任何小细节!细节是魔鬼。技术同仁需要有刨根问底的欲望和追求卓越的精神,只有这样才能不断成长和进步。

通过利用强大的工具如 show-busy-java-threads.sh,并遵循系统的故障排查方法,你可以快速识别和解决 Linux 服务器上的 CPU 使用问题。始终追求优化的代码性能,以确保系统的稳定性和效率。