问题描述
由于公司业务需要,将之前部署在阿里云上的Spring Boot应用迁移到了华为云的VPC上,之前在阿里云上应用基本上都是10秒内启动且看不到任何告警信息,迁移之后,不仅启动速度变慢到2到3分钟
,且时常能在日志中看到Temporary failure in name resolution
的告警信息。
以下是当前正在使用的服务器环境
- 服务器: 华为云VPC
- 操作系统:CentOS 7
- JDK版本: 1.8.0_151
- SpringBoot版本: 1.5.8.Rlease
- 容器: Embedded Tomcat 8.5.23
一开始对这个问题,是忽略的,由于本身应用属于内部开发阶段,且每天更新和部署不是那么频繁等等因素,一直迟迟没有想去解决这个问题。不过最近因为临近项目上线,提交也比较多,而且因为应用拆分和前后端分离的问题,本地调试比较麻烦,所以开始着手解决这个问题。
问题调查
-
首先看到这个异常,确实第一感觉是域名解析出的问题,于是考虑是不是Tomcat的域名解析服务出的问题,修改了EmbeddedTomcat的配置
修改之后测试发现问题依旧,去官网查了资料
其实Tomcat 8以后默认这个配置就是关闭的,这样看来自然就不是问题的根源了。 重新梳理了一下思路,Spring Boot的初始化肯定是先于Tomcat的,所以应该是Spring Boot的初始化过程中出现了阻塞导致应用启动过慢的问题。(
不过要说明的是,知道此时并不确信是因为域名解析导致的阻塞,因为Spring Boot在启动过程中并没有抛出任何异常
)
先做了个测试,启动Spring Boot应用的同时检查53端口,此时应用处于假死状态,应用和远程的DNS服务器正在通信,基本可以断定是因为调用了Jdk的InetAddress类的方法导致的问题。
于是通过jstack定位当前阻塞的代码
问题已经显而易见,Spring Boot在初始化的时候,执行了InetAddress的getLocalHost方法导致程序阻塞而影响了应用的启动时间。
又查看了一下当前服务器的DNS配置
可以看到DNS服务器的地址是公网的DNS地址,VPC访问不到导致取主机名的方法发生阻塞,因此导致应用启动时间非常长
问题解决
跟最初推测的差不多,确实是域名解析的问题,不过意外的是在Spring Boot初始化的时候获取VMID的时候会获取当前服务器的主机名。于是在/etc/resolve.conf中添加了本地主机的解析记录并删除无法访问的公网DNS配置,现在应用的启动已经和本地一样了
问题反思
虽然最开始就定位了问题是DNS,不过发生问题的根源一直没有确定,也不想只是简单的把DNS配置修改来立刻解决问题,毕竟希望究本溯源,不希望被表面的问题所迷惑。
于是又查看了JDK的源码,发现InetAddress#getLocalHost是个本地方法
本地方法也难不倒我们,下载了openjdk的源码找到对应的C源码
这个方法来自于jvm.cpp里面的方法
虽然对C表示不熟,不过基本能看出来是调用系统库的API来获取的主机名,顺便也查询了一下是说这个系统库本身设计的就不好,属于同步调用,在网络不通的情况下会导致严重的超时问题。
同时不免想对华为云吐槽几句,服务器装个图形界面版的CentOS就不说了,起码给个内网的DNS服务器啊,完全是没有经过任何配置优化的操作系统,期待更进一步的改进。