问题
springboot + dubbo
微服务部署在docker swarm
中,升级dubbo 版本到 2.7.14
后出现注册服务的IP不对,导致 dubbo 服务调用异常。
因为 docker swarm 环境下部署服务没法指定注册到注册中心的服务IP地址,此时dubbo注册服务的IP地址使用的是 org.apache.dubbo.common.utils.NetUtils#findNetworkInterface
方法获取的,该方法的源码如下:
/**
* Get the suitable {@link NetworkInterface}
*
* @return If no {@link NetworkInterface} is available , return <code>null</code>
* @since 2.7.6
*/
public static NetworkInterface findNetworkInterface() {
List<NetworkInterface> validNetworkInterfaces = emptyList();
try {
validNetworkInterfaces = getValidNetworkInterfaces();
} catch (Throwable e) {
logger.warn(e);
}
NetworkInterface result = null;
// Try to find the preferred one
for (NetworkInterface networkInterface : validNetworkInterfaces) {
if (isPreferredNetworkInterface(networkInterface)) {
result = networkInterface;
break;
}
}
if (result == null) { // If not found, try to get the first one
for (NetworkInterface networkInterface : validNetworkInterfaces) {
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
if (addressOp.isPresent()) {
try {
if (addressOp.get().isReachable(100)) {
// <==== 2.7.14 前的版本
// result = networkInterface;
// break;
// 2.7.14 前的版本 ====>
return networkInterface;
}
} catch (IOException e) {
// ignore
}
}
}
}
}
if (result == null) {
result = first(validNetworkInterfaces);
}
return result;
}
该方法用于查找合适的网络接口(NetworkInterface),逻辑如下:
- 获取所有有效的网络接口;
- 优先匹配系统指定的首选网络接口;
若未找到首选接口,则遍历接口地址,返回第一个可到达的接口;
在2.7.14
前的版本会一直遍历,然后返回最后一个可到达的接口
- 若仍未找到,则返回第一个有效接口。
我们看看 docker swarm 中容器的网络信息:
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:05:CD
inet addr:10.0.5.205 Bcast:10.0.5.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:319384 errors:0 dropped:0 overruns:0 frame:0
TX packets:337887 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:53199330 (50.7 MiB) TX bytes:44167534 (42.1 MiB)
eth1 Link encap:Ethernet HWaddr 02:42:AC:13:00:0E
inet addr:172.19.0.14 Bcast:172.19.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:46473 errors:0 dropped:0 overruns:0 frame:0
TX packets:48581 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:7550252 (7.2 MiB) TX bytes:4590693 (4.3 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:10034 errors:0 dropped:0 overruns:0 frame:0
TX packets:10034 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1318656 (1.2 MiB) TX bytes:1318656 (1.2 MiB)
/ #
从2.7.14版本注册服务的IP获取的是 eth1
,也就是虚拟网桥段(docker_gwbridge
)的地址,这个地址只能够在宿主机上访问,跨主机是不能访问的,正确的地址应该是eth0
(Overlay网络)。
解决方案
如果容器是多网卡的情况,在不手动指定注册服务的ip时都是不太稳定的,也就是说只能通过人为手动指定IP。但是在docker swarm中容器的ip是动态分配的,没办法提前指定。
使用服务名
在docker swarm 中 Service 提供内置的 DNS 解析和负载均衡能力,注册时使用docker swarm 中的服务名注册,这样也就能实现dubbo服务的正常调用了。
version: "3.7"
networks:
api-net:
external: true
services:
ucs:
image: xxx
networks:
- api-net
deploy:
replicas: 1
environment:
- DUBBO_IP_TO_REGISTRY=ucs
以上通过配置环境变量DUBBO_IP_TO_REGISTRY
设置了注册服务的ip为docker swarm 中的服务名。
但是这个方案在服务更新时,dubbo consumer 会出现短暂的消费异常:
org.apache.dubbo.rpc.RpcException: Failed to invoke the method getUser in the service com.mbzj.ucs.api.service.UserService.
No provider available for the service com.mbzj.ucs.api.service.UserService from registry RegistryDirectory(registry: zookeeper:2181)
-Directory(invokers: 1[ucs:20880], validInvokers: 0[], invokersToReconnect: 1[ucs:20880]) on the consumer 172.19.0.14 using the dubbo version 3.2.15. Please check if the providers have been started and registered.
大致的原因是新服务注册后,旧服务后注销导致,因为新旧服务的ip都是使用的域名 ucs。
指定网卡偏好
dubbo 支持 dubbo.network.interface.ignored
和 dubbo.network.interface.preferred
指定网络接口
version: "3.7"
networks:
api-net:
external: true
services:
ucs:
image: xxx
networks:
- api-net
deploy:
replicas: 1
environment:
- JAVA_TOOL_OPTIONS="-Ddubbo.network.interface.preferred=eth0"