虚拟网卡导致 apollo 启动太慢的问题

项目中集成了 apollo 之后, 在本地启动项目速度很慢, 比没有集成 apollo 的时候慢了 30s~40s.

这里做了一个简单的例子, 主要是一个 SpringBoot 的启动类和一个 apollo 的配置类, 配置类很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@EnableApolloConfig
public class HttpClientConfig {
@Value("${http.url}")
private String url;

@Value("${http.connectionTimeout}")
private String connectionTimeout;

@Value("${http.readTimeout}")
private String readTimeout;

@Value("${http.username}")
private String username;

@Value("${http.password}")
private String password;

...
}

启动项目:

1

可以看到一个很简单的只是集成了 apollo 的项目启动就花费了 39s 左右.到底是什么导致的呢?

可以使用 jvisualvm 进行观察:

2

可以看到大部分的时间都消耗在了 NetworkInterfaceManager#findValidateIp() 方法上面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public InetAddress findValidateIp(List<InetAddress> addresses) {
InetAddress local = null;
int maxWeight = -1;
for (InetAddress address : addresses) {
if (address instanceof Inet4Address) {
// has host name
// TODO fix performance issue when calling getHostName
if (!Objects.equals(address.getHostName(), address.getHostAddress())) {
weight += 1;
}
}
}
return local;
}

此方法要做的事情就是寻找有效的 IP, 另外可以看到此方法的 if (!Objects.equals(address.getHostName(), address.getHostAddress())) 片段也注释了 TODO fix performance issue when calling getHostName.

不过 jvisualvm 并没有明确的指示出是 InetAddress#getHostName() 方法耗时而导致 findValidateIp() 方法的耗时.换成 jProfiler 再重新观察一下:

3

此时就可以很明确的看出是 InetAddress#getHostName() 耗时而导致的问题.

关于 InetAddress#getHostName() 执行慢的问题可搜索下相关的话题.先看看有什么办法可以先 解决这个问题.

到这里我们可以想到的是有什么办法可以规避此方法的调用, 看下此方法的调用处, 也就是 NetworkInterfaceManager#load() 方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void load() {
// 从环境变量或系统属性获取 ip
String ip = getProperty("host.ip");

if (ip != null) {
try {
m_local = InetAddress.getByName(ip);
return;
}
}

try {
// 调用 findValidateIp()
local = findValidateIp(addresses);
}
}

从这里可以看到看出如果 getProperty("host.ip") 可以取到 ip 值, 那么 findValidateIp() 方法就不会被调用, 这样项目启动是不是就可以很快了? 在项目启动类加上 vm args: -Dhost.ip=IP地址, 重新启动项目:

4

此时可以看到项目启动非常快.

现在再思考下, 虽然 InetAddress#getHostName() 执行耗时, 但是也不至于那么慢, 如果真的这样, 那么岂不是只要用了这个方法的项目启动都会很慢.

再回头看看 NetworkInterfaceManager#load() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try {
// 获取所有的网卡
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
List<NetworkInterface> nis = interfaces == null ? Collections.<NetworkInterface>emptyList() : Collections.list(interfaces);
List<InetAddress> addresses = new ArrayList<InetAddress>();
InetAddress local = null;

try {
for (NetworkInterface ni : nis) {
if (ni.isUp() && !ni.isLoopback()) {
addresses.addAll(Collections.list(ni.getInetAddresses()));
}
}
// 传入地址列表
local = findValidateIp(addresses);
} catch (Exception e) {
}

findValidateIp() 方法接收的是一个地址列表, 在本机调试, 在使用有线网的情况下 addresses 参数的个数是 10(无线网是 12).为什么会这么多, 因为本机安装了虚拟机, 多出了几块虚拟网卡, 查看网络连接:

5

此时可以想到是不是因为是虚拟网卡而导致的问题? 另外 NetworkInterfaceManager#load() 方法中有 ni.isUp() 的判断, 此方法会判断网卡是否启用并运行.将除了以太网和 WLAN 之外的网络都禁用, 此时再重新运行项目, 发现项目启动速度就很快了:

6

从这个例子中可以看到, 如果项目集成 apollo 之后在本地测试启动速度很慢, 那么可以看下本地是否安装了虚拟机, 或者可以通过传入 vm args -Dhost.ip=IP地址 的方式来解决项目启动慢的问题.

Yaml 配置集合元素过多导致的 SpringBoot 启动过慢
confluence-记一次 confluence markdown 表格样式不美观发生的事