我们为什么不采用ning的AsyncHttpClient

坦白的说,现在的量没有到非要用高性能异步httpclient的时候,用jdk自身提供的URLConnection足够了,最多为便捷性封装一下。真要考虑性能的话还有其他的解决方案未必一定要选择AsyncHttpClient。它依赖了netty已经不那么轻巧;另外去年我们遇到过tomcat不能正常退出的情况,发现是由这个框架里的某个non-daemon线程引起的,当时在微博上贴过:

ning的AsyncHttpClient 里启动了一个 netty 的 HashedWheelTimer 线程,这个线程挺有意思的,本来父线程已经是 daemon 的了,但使用 jdk 的 DefaultThreadFactory 给显式的 setDaemon(false) 了,致使这个线程一直以 non-daemon 方式运行, tomcat正常的stop没法结束这个 non-daemon 线程,要kill才行

"Hashed wheel timer #3" #44 prio=5 os_prio=0 tid=0x000000000a3a5000 nid=0x1ac5 waiting on condition [0x0000000043c70000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at org.jboss.netty.util.HashedWheelTimer$Worker.waitForNextTick(HashedWheelTimer.java:445)
    at org.jboss.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:364)
    at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
    at java.lang.Thread.run(Thread.java:745)

估计作者并未考虑在servlet容器里使用的情况,当时没有找到有效的方法解决,也没时间去深入了解,直接切换到JDK的URLConnection

当然我是针对我们的场景,如果你在使用AsyncHttpClient时也遇到non-daemon线程的问题,可以参考一下当时姜宁的回复(可以给AsycnClient提个patch, Netty HashedWheelTimer 的构造支持替换 ThreadFactory),对创建HashedWheelTimer的线程工厂做一下手脚。

jstack不可用的原因

上午遇到的jstack没有输出的问题,印象里在阿里前几年也遇到过,不记得细节了;晚上查了一下,是jdk1.6的某些特定版本存在的bug,见这篇blog,简单的说就是本来hsperfdata_$user 这个目录一直约定是在系统的临时目录下的,对linux来说是”/tmp”

用 strace 来看一下正常版本启动jvm,hsperfdata_$user目录不受java.io.tmpdir变量影响

$ strace -ff -e trace=file  java -Djava.io.tmpdir=/data/test -version 2>&1 | perl -ne 's/^[^"]+"(([^\\"]|\\[\\"nt])*)".*/$1/ && print'  | grep hsperfdata

/tmp/hsperfdata_hongjiang
/tmp/hsperfdata_hongjiang/17019

而在 jdk1.6.0_21到24引入了bug,使用了jvm的环境变量-Djava.io.tmpdir做为了hsperfdata_$user的路径,正好一些应用比如tomcat是对这个变量设置了自己的路径的,比如”/server/tomcat/temp”,存在bug的这些版本把hsperfdata_$user目录也放到tomcat环境变量指定的目录下了,结果 jps, jstack, 之类工具都不能找到目标了。根据bug报告里给出的一种变通方式,在运行jstack时设置-J-Djava.io.tmpdir=$CATALINA_HOME/temp 也并未能工作。对于这个问题最佳的处理方式还是升级jdk版本。

补充:

虽然对jstack设置变量并不工作,但发现对jinfo设置该变量是有效的,比如:jinfo -J-Djava.io.tmpdir=/server/tomcat/temp -sysprops $pid 在jdk1.6.0_24下是可以工作的。

通过strace查看一个进程的标准输出内容

线上某个业务java进程出了一些麻烦,去诊断的时候发现jstack无法输出,可能是jdk或os版本的问题。这时还可以尝试一下kill -3,它默认会输出到进程的标准输出。如果不幸这个进程的标准输出被重定向到了 /dev/null 或者重定向到某个文件,但却因为很多其他日志也在大量的输出,导致日志文件过大,要从中找出线程栈相关的日志,还要耗点时间;这个时候,可以通过strace来跟踪一个进程的标准输入。

strace输出的信息需要一些处理,可以通过管道与其他命令组合(通过-o参数或-ff参数)

这里 -o 参数后边是一个字符串表示输出文件,如果字符串开头是一个"|"会被strace识别为管道

$ strace -f -e trace=write -e verbose=none -e write=1,2 -q -p $pid -o "| grep '^ |' | cut -c11-60 | sed -e 's/ //g' | xxd -r -p"

或者 -ff 参数,用管道与其他命令组合,注意strace的错误输出也要重定向

$ strace -ff -e trace=write -e write=1,2 -s 1024 -q -p $pid 2>&1 | cut -c11-60 | sed -e 's/ //g' | xxd -r -p

不过这两种方式都遇到一个问题,因为buffer的问题导致管道后边的命令没能完全处理strace跟踪到的数据(要等到后续的数据塞满buffer),有点像grep需要--line-buffer解决缓冲区问题,但不知道这里有什么方式,尝试过stdbuf也没有解决。最后只能分两步,先把strace的内容输出到文件,然后再对内容解析:

$ strace -f -e trace=write -e write=1,2 -q -p $pid  -o /tmp/slog

$ grep " |" /tmp/slog | cut -c11-60 | sed -e 's/ //g' | xxd -r -p

starling和kestrel这两只鸟的来由

虽然SK组合子早有所闻,但这两个字母所代表的意思却一直不知道,原来是出自于《To Mock a Mockingbird and Other Logic Puzzles》这本书,作者用starlingkestrel这两只鸟来演绎哥德尔的证明。SK是这两只鸟的缩写,我是看到这篇Blog才了解到的。

早先只知道starlingkestrel是twitter的消息中间件产品,原来产品名还有这个来由。