记一次利用TOP命令排查压测问题经过

时间:2024-05-23 16:05:48

梦开始的地方

  近期,负责的接口开始上压测环境,第一次接触压测,对能压到多少TPS并没有多少概念。然而结果还是十分出乎意料,“单接口TPS100+左右,并发50,100,200的结果基本稳定,接口请求有点慢,而且CPU占用率很异常,四核CPU都跑满了,不是很合理,排查下什么原因。”测试如是说。

记一次利用TOP命令排查压测问题经过

记一次利用TOP命令排查压测问题经过

  询问了下组长上一版的时候其他接口的压测结果,组长回复不至于这么慢。于是开始排查自己代码的问题。

  记一次利用TOP命令排查压测问题经过

排查代码问题

   Review代码。首先想,是不是自己的代码有问题,将代码发给有压测经验的同事帮忙review,并自己对代码review。同事依据经验判断,4核8G的机器不应该会这么慢。

   CPU占用率较高。由于自己代码中用户ID在首次请求到接口会走一个INSERT或UPDATE分支否则只走查询分支,INSERT或UPDATE分支都有生成唯一键的需求,这部分参考了美团的leaf分布式ID生成算法,即号段思想,获取一个号段后,在本地利用 AtomicLong 进行自增(我把获取号段这部分由数据库改成了redis,并简化了算法,只参照了思路)。AtomicLong 底层是采用CAS的,也就是在并发情况下会进行自旋,导致CPU资源的占用。

   验证CPU占用率的猜想。将生成唯一Key的方法替换为redis自增。让测试重新再次压测,并没有太多变化,排除该问题。

   接口请求速度过慢。没什么思路,请教了技术经理。技术经理review代码后,觉得可能是插入和更新等操作影响了,建议给测试一些只会走查询分支的再看看,于是导出一份只走查询分支的数据,让测试重新压测,TPS还是在180左右,且CPU占用率还是十分异常,且测试说之前压测的数据库没什么压力,应该不是这个问题。

   排查具体哪一部分调用占时过高。技术经理建议将代码中调用到ms层(即数据库层到返回到接口层)的部分,都打上调用时间日志,并在ms层底层也打上调用时间日志。打印日志后,再次进行压测,排查日志,发现ms查询数据库只需要3ms,但是从请求ms层到返回接口层耗时高达100+ms,怀疑是平台组框架问题。于是尝试单压接口层check接口,即无逻辑接口,请求后调用ms层check直接返回。自己尝试单压了下check接口,结果tps在280+,技术经理表示这种接口至少得有几千以上的TPS,可能是平台组框架问题。但是组长表示之前到此次压测并没有升级过平台组框架。

   但是单压check接口还是提供了一个排查方向,不是我代码的问题,不过问题还是没得到解决,继续排查。

  

尝试排查服务器中CPU占用情况

   向组长要来了压测机器的权限,之前自学了jmeter,所以可以自己进行压测,这样就不用频繁去喊测试了。连上服务器,打开jmeter写好脚本开始压测,并用top命令监控CPU占用情况。

   top命令能查看到java进程占用CPU情况,由于问题已修复切当时没有截图,这里就不复现了。

记一次利用TOP命令排查压测问题经过

  再利用top -Hp pid 可以查看到指定进程的线程情况。

记一次利用TOP命令排查压测问题经过

  这里的PID为线程的ID,当时排查的时候,有一个线程CPU占用率十分之高。这时候我们可以利用jstack命令查看该线程的调用栈信息。

  jstack打印的信息大概为:

记一次利用TOP命令排查压测问题经过

我们主要关注三个点

nid: 是我们用top -Hp pid 命令看到的,其中的线程的PID转16进制,比如我们想查看PID为 15852 线程的调用栈,我们可以使用 jstack 15654 | grep '3dec',借助这个我们可以排查到指定线程的调用栈。(15654为我们第一次TOP命令看到的java进程的PID,3dec为 15852  的16进制)

java.lang.Thread.State: 线程当前状态,WAITING代表一个正在无限期等待另一个线程执行一个特别的动作的线程处于这一状态。这是线程释放了锁,但也没有继续执行,处于WAITING 和 BLOCKED 状态下的线程需要重点关注。全部状态有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED ,可自行查阅资料。

at java.util.xxx: 这些是调用栈信息,可以快速定位到代码。

 

问题解决

排查时,查看到CPU占用最高的线程为

记一次利用TOP命令排查压测问题经过

可以看到调用栈中

io.github.logtube.core.BaseFileOutPut 可以推测出是输出到日志文件的线程占用了大量的CPU资源。自己代码中并没有什么日志打印,猜想是日志等级过低,把DEBUG日志也输出到了日志文件里,并发时大量的HTTP请求信息DEBUG的日志写入,并且日志的写入和读取使用了ArrayBlockingQueue,由于阻塞队列的写入和读取都需要lock操作,这些都会导致频繁的加锁和锁等待,而且日志量级过多也有大量的IO,Http也需要占用IO,可能就导致了Http线程响应时间过长,TPS下降。这部分由于自己对操作系统认识还不够,大概推测是这些原因导致cpu拉满,排查当时没有关注IO占用情况。

记一次利用TOP命令排查压测问题经过

由于项目配置是统一配置的,之前也说没有这种情况出现,就自己先确认了下,grep 'DEBUG' | less 了服务器日志,果然发现了大量的DEBUG日志打印,查看压测环境配置,发现了这么一行配置。

记一次利用TOP命令排查压测问题经过

把 logging.level.root 修改为INFO,提交压测环境。再叫测试压测,单接口TPS达到 2800/s,接口性能提高 2800%,测试直呼牛逼,但我心里只有MMP。

终于破案,日志问题导致。

总结:

1.出现CPU占用过高这种情况,可以直接先用top,jstack组合排查。

2.总结其他人的经验,排除一些不存在的情况。

3.需要多学习操作系统、Java线程状态和锁的相关知识,防止有其他异常情况出现。