问题现象
在远程机器执行部分任务的shell命令时,偶尔会卡住,但是实际任务已经执行完成了。大部分应用都没有问题,只有部分应用在执行一个自研的插件命令时会卡住。将任务命令拷贝手工在机器上执行也是没有问题的。该服务已运行近一年,执行了大量的任务都没有这种问题,初步怀疑是该插件的问题,但是手动执行插件命令也没问题的,可能是插件触发了一个bug。
分析
伪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| JSch jsch = new JSch();
Session session = jsch.getSession(username, host, 22); session.setPassword(password); session.setConfig("StrictHostKeyChecking", "no"); session.connect();
Channel channel = session.openChannel("exec"); ((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
InputStream out = channel.getInputStream();
InputStream err = channel.getErrStream(); channel.connect();
byte[] tmp = new byte[1024]; while (true) { while (out.available() > 0) { int i = out.read(tmp, 0, 1024); if (i < 0) break; log.info(new String(tmp, 0, i)); } if (channel.isClosed()) { if (out.available() > 0) continue; if (channel.getExitStatus() != 0) { log.info(err); throw new Exception("exitStatus:" + channel.getExitStatus()); } break; } Thread.sleep(1000); } channel.disconnect(); session.disconnect();
|
原因
经过对比日志,发现这些卡住的任务,手动执行时输出到终端的日志有非常多的warn日志。然后认真查看代码发现标准错误日志没有实时打印,只是在最后命令退出码不是0时,才会打印标准错误输出。添加日志打印err.available()的大小发现到32768时就会卡住。查询资料发现java内部缓冲区缺省大小是 32 KB。当超过这个大小,缓冲区就会阻塞等待消费,此时其实相当于发生了死锁。解决方式也很简单,就是把标准错误日志也实时打印出来,避免缓冲区塞满。
1 2 3 4 5
| while (err.available() > 0) { int i = err.read(tmp, 0, 1024); if (i < 0) break; log.info(new String(tmp, 0, i)); }
|
还有一个问题,为啥其他任务没有出现这种情况,只是公司自研的插件出现这个问题呢?
经过定位,因为该插件使用的是JDK自身的日志库java.util.logging.Logger,而该库info及以上级别的日志都是输出到标准错误输出(System.err)。所以该插件输出大量日志时就会导致卡住。