背景
基本信息
现象
应用进程cpu占用200%+,业务无法成功执行完成,并在达到超时时间后不断触发重试,导致堆积大量待处理的消息。
虽然每个应用的业务不尽相同,但是排查的套路大体是一致的:
由于我的应用没有异常日志,比较明显的是cpu占用很高,因此首先利用jstack去排查到底是谁耗用了那么高的cpu。
首先,查看当前应用的进程id
ds_dfpas@dev22162:~> jps
6741 supervisor
3070 LogWriter
3086 worker
2787 LogWriter
25301 Kafka
19411 QuorumPeerMain
2155 worker
2139 LogWriter
2804 worker
3562 Jps
假设这里是3086这个进程有问题,接着
top -Hp 3086
top -Hp可以看到该进程内线程的cpu占用情况,按照从高往低排列。和我们看top -p pid的时候差不多
线程占用cpu
我们可以看到,id为3097的线程占用了22%的cpu,那么它在做什么?是什么线程呢?
这时候,我们需要做的就是将这个进程执行的stack dump出来,然后根据线程id去找这个线程的执行栈。
首先,利用jstack快照线程堆栈,
jstack 3086 > stack.log
接着我们需要将刚才的线程id转换为十六进制数字,因为在stack中是用16进制标识线程id的
ds_dfpas@dev22162:~> printf "%x\n" 30979
7903
然后,根据这个结果,去刚才的stack.log里找对应的线程,nid=0x7903
"Thread-482" prio=10 tid=0x00000000022b5000 nid=0x7903 runnable [0x00007f27f48c7000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:345)
at java.util.zip.DeflaterOutputStream.deflate(DeflaterOutputStream.java:253)
at java.util.zip.DeflaterOutputStream.write(DeflaterOutputStream.java:211)
at java.util.zip.GZIPOutputStream.write(GZIPOutputStream.java:146)
- locked <0x00000007abd7bab0> (a java.util.zip.GZIPOutputStream)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:126)
- locked <0x00000007abd7bad0> (a java.io.BufferedOutputStream)
at org.apache.logging.log4j.core.appender.rolling.action.GzCompressAction.execute(GzCompressAction.java:102)
at org.apache.logging.log4j.core.appender.rolling.action.GzCompressAction.execute(GzCompressAction.java:78)
at org.apache.logging.log4j.core.appender.rolling.RollingFileManager$AsyncAction.execute(RollingFileManager.java:226)
at org.apache.logging.log4j.core.appender.rolling.action.AbstractAction.run(AbstractAction.java:65)
- locked <0x00000007abd65e58> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager$AsyncAction)
at java.lang.Thread.run(Thread.java:745)
可以看到,nid=0x7903的线程,处于runnable状态,其是log4j的记日志的线程。其实到这里,大致就知道为何该线程占用cpu这么高了,因为日志记录的太平凡。而且这里是在做压缩。联系到实际业务,我的确打了大量的日志,且日志配置了没100M滚动一次,并且执行压缩。
于是,对于刚才看到的这个比较耗cpu的线程,算是找到原因了。那么解决方式也很好办,就是我关闭了所有的日志,然后继续排查。
关闭日志很简单,就是log4j的配置文件调整成为error级别,我没有打印error级别的日志,因此不会再有日志输出。不过我这里使用的是log4j2,可以动态加载配置。
之所以说继续排查,是因为我们只是找到 cpu占用高的一个线程,或者是某一类线程,但是理论上这不会导致业务流程走不下去,顶多是慢,但也应该缓慢执行成功一部分。
紧接着,继续结合top和top -Hp观察进程和线程的cpu占用情况,发现cpu占用问题有效缓解,基本处于正常状态(70%多),top -Hp也未发现比较异常的线程。但是业务还是没有执行成功,仍然在不停的重试。
这时候我怀疑是流程走到某个地方就走不下去了导致的,就没有再去抓个别线程来,而是干脆直接再次dump出执行栈。
其实这个过程需要结合自己的应用分析,比如是不是外部访问太慢导致,是不是自己某个地方没有打印出来异常,是不是某个地方有死循环等等,如果都没有明显的结论,那么至少有两种途径可以继续排查,一个是阅读堆栈信息,一个是查看gc情况,是不是频繁的fullgc倒是stw。其实我是怀疑过fullgc的,也dump出来了内存快照,结合mat分析过。mat给出了一些疑点,但是结合代码和应用排除了,因此我只有选择尝试一下阅读stack
继续
jstack 3086 > stack.log
然后仔细阅读一下stack,于是我发现了一个重要的信息
"Thread-215-AndroidDfpSimilarityBolt-executor[113 113]" prio=10 tid=0x0000000000f3c800 nid=0x2927 waiting for monitor entry [0x00007f38bc03f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
at com.cup.dcs.storm.persistence.DbDao.queryFromDB5Times(DbDao.java:260)
at com.cup.dcs.storm.persistence.DbDao.zrangeFixLen(DbDao.java:222)
at com.cup.dcs.storm.persistence.PersistenceDao.zrange(PersistenceDao.java:145)
at com.cup.dcs.storm.bolt.dfp.AndroidDfpSimilarityCalBolt.execute(AndroidDfpSimilarityCalBolt.java:117)
at org.apache.storm.daemon.executor$fn__7953$tuple_action_fn__7955.invoke(executor.clj:728)
at org.apache.storm.daemon.executor$mk_task_receiver$fn__7874.invoke(executor.clj:461)
at org.apache.storm.disruptor$clojure_handler$reify__7390.onEvent(disruptor.clj:40)
at org.apache.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:439)
at org.apache.storm.utils.DisruptorQueue.consumeBatchWhenAvailable(DisruptorQueue.java:418)
at org.apache.storm.disruptor$consume_batch_when_available.invoke(disruptor.clj:73)
at org.apache.storm.daemon.executor$fn__7953$fn__7966$fn__8019.invoke(executor.clj:847)
at org.apache.storm.util$async_loop$fn__625.invoke(util.clj:484)
at clojure.lang.AFn.run(AFn.java:22)
at java.lang.Thread.run(Thread.java:745)
这是一个重要的信息,我的应用的某些线程被BLOCKED住了,继续搜索BLOCKED发现,大量的线程被卡在类似的地方(JdbcPool.getConnection),
ds_dfpas@dev22161:~> grep -A2 BLOCKED stack.log.blocked
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
--
java.lang.Thread.State: BLOCKED (on object monitor)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:69)
- waiting to lock <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
大家都在等着锁住0x000000078194d320,去搜索0x000000078194d320,发现罪魁祸首:
"Thread-185-AndroidDfpSimilarityBolt-executor[83 83]" prio=10 tid=0x00000000017fa800 nid=0x2909 runnable [0x00007f38bde5b000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:152)
at java.net.SocketInputStream.read(SocketInputStream.java:122)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:112)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:159)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:187)
- locked <0x00000007e25e67b8> (a com.mysql.jdbc.util.ReadAheadInputStream)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3158)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3615)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3604)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4149)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2615)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2776)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2834)
- locked <0x00000007fda34988> (a com.mysql.jdbc.JDBC4Connection)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2783)
at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1569)
- locked <0x00000007fda34988> (a com.mysql.jdbc.JDBC4Connection)
at com.mysql.jdbc.ConnectionImpl.loadServerVariables(ConnectionImpl.java:4219)
at com.mysql.jdbc.ConnectionImpl.initializePropsFromServer(ConnectionImpl.java:3614)
at com.mysql.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:2370)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2328)
- locked <0x00000007fda34988> (a com.mysql.jdbc.JDBC4Connection)
at com.mysql.jdbc.ConnectionImpl.(ConnectionImpl.java:832)
at com.mysql.jdbc.JDBC4Connection.(JDBC4Connection.java:46)
at sun.reflect.GeneratedConstructorAccessor36.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:417)
at com.mysql.jdbc.LoadBalancingConnectionProxy.createConnectionForHost(LoadBalancingConnectionProxy.java:382)
- locked <0x0000000781ef6618> (a com.mysql.jdbc.LoadBalancingConnectionProxy)
at com.mysql.jdbc.RandomBalanceStrategy.pickConnection(RandomBalanceStrategy.java:75)
at com.mysql.jdbc.LoadBalancingConnectionProxy.pickNewConnection(LoadBalancingConnectionProxy.java:693)
- locked <0x0000000781ef6618> (a com.mysql.jdbc.LoadBalancingConnectionProxy)
at com.mysql.jdbc.LoadBalancingConnectionProxy.(LoadBalancingConnectionProxy.java:335)
at com.mysql.jdbc.NonRegisteringDriver.connectLoadBalanced(NonRegisteringDriver.java:393)
at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:326)
at java.sql.DriverManager.getConnection(DriverManager.java:571)
at java.sql.DriverManager.getConnection(DriverManager.java:215)
at com.cup.dcs.storm.persistence.JdbcPool.getNewConnection(JdbcPool.java:116)
at com.cup.dcs.storm.persistence.JdbcPool.getConnection(JdbcPool.java:95)
- locked <0x000000078194d320> (a com.cup.dcs.storm.persistence.JdbcPool)
at com.cup.dcs.storm.persistence.DbDao.queryFromDB5Times(DbDao.java:260)
at com.cup.dcs.storm.persistence.DbDao.zrangeFixLen(DbDao.java:222)
at com.cup.dcs.storm.persistence.PersistenceDao.zrange(PersistenceDao.java:145)
at com.cup.dcs.storm.bolt.dfp.AndroidDfpSimilarityCalBolt.execute(AndroidDfpSimilarityCalBolt.java:117)
at org.apache.storm.daemon.executor$fn__7953$tuple_action_fn__7955.invoke(executor.clj:728)
at org.apache.storm.daemon.executor$mk_task_receiver$fn__7874.invoke(executor.clj:461)
at org.apache.storm.disruptor$clojure_handler$reify__7390.onEvent(disruptor.clj:40)
at org.apache.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:439)
at org.apache.storm.utils.DisruptorQueue.consumeBatchWhenAvailable(DisruptorQueue.java:418)
at org.apache.storm.disruptor$consume_batch_when_available.invoke(disruptor.clj:73)
at org.apache.storm.daemon.executor$fn__7953$fn__7966$fn__8019.invoke(executor.clj:847)
at org.apache.storm.util$async_loop$fn__625.invoke(util.clj:484)
at clojure.lang.AFn.run(AFn.java:22)
at java.lang.Thread.run(Thread.java:745)
该线程持有着0x000000078194d320的锁~根据堆栈信息和代码,可以知道这里实在获取新的数据库连接。那么问题就比较清楚了,获取连接的时候除了问题,导致锁被独占,其他的线程都在等着。而该线程一直获取不到连接,因此整个应用都被卡住了。
为了验证这个连接的问题,用mysql客户端手动连接了一下数据库,果然有问题,执行连接命令之后,既不成功,也不返回refused等信息,而是一直hang在那里~这可能和我们使用了某个不成熟的代理有关,咨询了相关同事后,重启了代理,连接可以正常拿到,业务恢复正常,问题解决!!!
问题找到之后发现其实问题很简单,就是mysql的代理出问题了,但是坑的是,这个代理并没有立刻拒绝或者超时返回,也没有抛出异常,而笔者又没有立刻怀疑到mysql的问题,因此有了前面一堆的猜想和验证。不过虽然饶了些路,但是排查的过程还是有意义的,毕竟真理虽然至简,但是发现真理的道路确实复杂、困难的。
在前面也说过,一般出了问题,可以先结合日志分析,如果日志没有信息,则可以怀疑一下关键路径,比如外部服务、数据库、缓存等,如果这些都无法得出重要信息,那就祭出最直接的工具吧,要么jstack分析栈执行情况,要么结合gc和jmap来分析内存使用情况。
另外,在使用jstack结合top -Hp的时候,需要注意的是,动作要快!因为不同时期,stack快照出的线程堆栈信息是不完全相同的,如果方法执行完了,那么方法栈就会结束。因此一般建议的方式是
首先
top -Hp pid
观察线程执行和cpu占用情况,发现需要跟踪的线程时,立刻摁q退出观察,然后迅速执行
jstack pid > stack.log
导出之后再去进行线程id的十六进制转换和grep,比较容易成功抓到。
另如果是64位机器,可能需要在jstack的时候添加-J-d64
欢迎关注公众号:“架构一线”,定期分享一些实战心得,互联网前沿技术等.