彻底找到 Tomcat 启动速度慢的元凶
问题现象
美女同事找我解决一个问题,说 Tomcat 启动很慢。开始我以为是程序写的问题,所以把 webapps 下所有程序都删除掉。(只保留 Tomcat 自带)灵异的事情发生了,Tomcat 停止在—— 我查看了一下进程,Tomcat 所在的 JVM 进程已经被启动了所以可以排除是 JVM 退出引起的问题。那么问题真的就是 JVM 因为某种原因被 阻塞 了。
分析
问题比较棘手,我排除了 CPU、内存不足引起的问题;排除了硬盘空间不足引起的问题;我甚至去观察了网络 I/O、硬盘 I/O 情况,都非常正常。程序被 阻塞 一般来说一定是要等待某个资源,而现在的情况是所有资源都充足,所以我几乎想不到是什么问题引起的。 我开始怀疑是 KVM Hypervisor 虚拟化的问题(用的是虚拟机)我改变了策略在 VMWare 开了两台虚拟机上直接下载 Tomcat 启动。其中一台很快启动,另一台居然也被 阻塞 ,问题被重现了。 眼看要在美女面前丢脸,我光辉伟岸的形象要荡然无存。这种情况下我不能去 撸
代码吧?
况且 Tomcat 那么多人用,真有这么明显的 Bug 早就炸开锅了。(Tomcat 还是很靠谱的不像 xxxxStack 那么狗屎)仔细想想我需要找到 Tomcat 停止在了哪里?代码里发生了什么事情,但是我又不可能去撸代码。无可奈何的情况下我决定试一下 strace ,这是一个 跟踪系统调用(System Call)的工具 ,无论是 Java 还是 Pyhton 很多资源申请都会变成都会变成 System Call。(比如打开文件、新建线程、读写数据、等待 I/O)通过这个工具我至少可以知道 Tomcat 是停止在哪个 System Call 上的 ,这样可以方便我推断出问题的原因。
strace -f -o strace.out ./catalina.sh run
strace 有很多参数,我用了二个参数
- -f 跟踪 fork 的子进程,通俗的说会跟踪所有线程的系统调用
- -o 把内容输出到文件
其他参数请自行搜索 下面分析 strace.out 文件,分析的方法是从下往上(被 阻塞 的地方肯定是在最后咯)。首先我们需要去掉 Tomcat 停止引起的 System Call,它们不是我们需要的。从后往前搜索找到 SIGINT 红色部分以上就是引起阻塞的系统调用了,上面有一大堆一大堆的 futex 的调用,它是 Linux 中的一种轻量级的同步方法,所以我们可以判断出最上面肯定是有某个 System Call 就是 阻塞 的真正元凶。
跳过所有的 futex : 这个 read 就是引起后面一串 futex 的真正原因, strace 非常聪明它不仅仅给出了 System Call 还给出了传递的参数和返回值,read 读取的是 51 号文件句柄,没有返回成功(unfinished)。 顺着这条路,我们看一下 51 号文件句柄是什么 /dev/random
是 Linux 下的随机函数生成器,读取它相当于生成随机数字。搜索它,第一个是 wiki 至此似乎一切真相大白了, /dev/random
会根据 噪音 产生随机数,如果 噪音 不够它就会阻塞。Linux 是通过 I/O,键盘终端、内存使用量、CPU 利用率等方式来收集 噪音 的,如果 噪音 不够生成随机数的时候就会被 阻塞 。
深入分析
如果用 Tomcat /dev/random
作为关键字基本上就能够回答我们的疑惑了。Tocmat 的 Session ID 是通过 SHA1 算法计算得到的,计算 Session ID 的时候必须有一个密钥。为了提高安全性 Tomcat 在启动的时候回通过随机生成一个密钥。 在 http://wiki.apache.org/tomcat/HowTo/FasterStartUp (Entropy Source 部分)有一段解释。stackoverflow 上面也有一大批这方面的说明,所以这里就不再多做介绍。 明白了问题的原因解决起来就非常简单了——替换 /dev/random
为 /dev/unrandom
,用伪随机函数生成器(/dev/urandom)来替代随机函数生成器(/dev/random)。
- 通过修改 Tomcat 启动文件
-Djava.security.egd=file:/dev/urandom
- 通过修改 JRE 中的
java.security
文件securerandom.source=file:/dev/urandom
当然 JVM 的开发者不是傻瓜,Tomcat 的开发者也不是二百五。他们之所以没有选择 /dev/urandom
是为了提高系统的安全性, /dev/urandom
并不是真正的随机行为。(其实一般情况下 /dev/urandom
也是足够安全的不太容易被“重复”)
彻底解决问题
上面介绍的两种方式都是用/dev/urandom 替换/dev/random,其实还有第三种方式——增大/dev/random 的熵池。问题的原因是由于熵池不够大,所以增大它是最彻底的方法。 通过 cat /proc/sys/kernel/random/entropy_avail 我们可以查看现在的熵池大小;我们需要找到一种方式来提高这个值就行了。
如果你的 CPU 带有 DRNG 特性,可以充分利用硬件来提高熵池产生的速度 。通过 cat /proc/cpuinfo | grep rdrand 可以查看自己的 CPU 是否支持,一般来说 Intel 的 Ivy_Bridge 架构的 CPU 都支持(i3、i5 需要注意是否采用该种架构,i7 和 xeon 基本上都支持);AMD 的 CPU 在 2015 年以后生成的都支持。(如果你是虚拟机需要开启额外的参数)。如果你的硬件不支持,也没有关系,我们可以让/dev/unrandom 来做“熵源”。 以 Centos7 为例,
yum install rngd-tools
安装 rngd 服务(熵服务)systemctl start rngd
启动服务
如果你的 CPU 不支持 DRNG 特性或者像我一样使用虚拟机,可以使用/dev/unrandom 来模拟。
cp /usr/lib/systemd/system/rngd.service /etc/systemd/system
编辑 /etc/systemd/system/rngd.service
service 小结, ExecStart=/sbin/rngd -f -r /dev/urandom
systemctl daemon-reload
重新载入服务systemctl restart rngd
重启服务
经过上面的修改,我们再观察 /proc/sys/kernel/random/entropy_avail 基本上在 3000 左右。我们可以测试一下随机数的生成速度
watch -n 1 cat /proc/sys/kernel/random/entropy_avail
观察这个值
新打开一个 shell,用 dd 命令测试随机数。 dd if=/dev/random of=random.dat count=40960
[root@localhost bin]# dd if=/dev/random of=random.dat count=40960
记录了 0+40960 的读入
记录了 6004+1 的写出
3074362 字节(3.1 MB) 已复制,5.01017 秒,614 kB/秒
5 秒产生了 40960 个随机数,/proc/sys/kernel/random/entropy_avail 会有剧烈的变化,所有随机数产生之后它又会保持在 3000 左右。
选择哪种解决方法
个人建议选择第三种方式,熵池不仅仅 Tomcat 用,Linux 下的所有应用程序产生随机数都会用到这个,所以不仅仅是 Tomcat 可能被 阻塞 。如果你搜索会发现 Apache、Nginx、OpenSSL 都被这个问题 坑过 。如果我们通过修改 Java 的配置来解决这个问题其实只是解决 Java 应用程序的问题,只能是治标不治本。根治的方法应该是通过 rngd 提高随机数生成的速度。
总结
经验不是经历。用别人的经验解决一个问题不难,难的是自己从头走一遍这条路,更加难的是推翻前人的经验对一个问题能够有自己的看法和领悟。 这个案例加深了我对 strace 的理解,对于 空中加油 这种类型的系统调试有了自己的经验;通过对原因的深入分析我找到了 更好的办法 。这就是 康德精神 ——思考、批判、理性。
如何重现故障
可以很容易的重现文章中描述的故障
systemctl stop rngd
停止 rngd 服务(如果你有启动 rngd)
查看当前熵池的大小 cat /proc/sys/kernel/random/entropy_avail
head -c1024 /dev/random
,强制消费 1024 个随机数,系统会长时间没有反应。直接 ctrl+c
再次查看熵池的大小 cat /proc/sys/kernel/random/entropy_avail
,保证它的大小在尽可能的小
启动 tomcat,会发现长时间很长时间的等待。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论