项目场景:
dubbo服务所在的机器存在多网卡的情况。
问题描述
dubbo服务在启动过程中,如果存在多网卡的情况,例如 : 连接了vpn,则在注册服务时,可能会注册上vpn的ip,导致内网的服务无法被访问。
原因分析:
通过查看dubbo源码,可以发现dubbo的服务向注册中心暴露的url,是如何拼接host和端口的。
private URL buildUrl(ProtocolConfig protocolConfig, Map<String, String> params) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = "dubbo";
}
//获取host
String host = findConfiguredHosts(protocolConfig, this.provider, params);
//获取port
Integer port = findConfiguredPort(protocolConfig, this.provider, this.getExtensionLoader(Protocol.class), name, params);
URL url = new ServiceConfigURL(name, (String)null, (String)null, host, port, (String)this.getContextPath(protocolConfig).map((p) -> {
return p + "/" + this.path;
}).orElse(this.path), params);
if (this.getExtensionLoader(ConfiguratorFactory.class).hasExtension(((URL)url).getProtocol())) {
url = ((ConfiguratorFactory)this.getExtensionLoader(ConfiguratorFactory.class).getExtension(((URL)url).getProtocol())).getConfigurator((URL)url).configure((URL)url);
}
URL url = ((URL)url).setScopeModel(this.getScopeModel());
url = url.setServiceModel(this.providerModel);
return url;
}
获取host分别有三个源头 : host的获取顺序优先级 分别是 protocolConfig >> provider >> 本地网卡地址
private static String findConfiguredHosts(ProtocolConfig protocolConfig, ProviderConfig provider, Map<String, String> map) {
boolean anyhost = false;
//从系统环境变量去获取host ,key为DUBBO_IP_TO_BIND
String hostToBind = getValueFromConfig(protocolConfig, "DUBBO_IP_TO_BIND");
//注意这里 host不能为 127.0.0.1 和 localhost ,至于为什么不能为环回地址 此处不是很了解为啥这样设计???
if (hostToBind != null && hostToBind.length() > 0 && NetUtils.isInvalidLocalHost(hostToBind)) {
throw new IllegalArgumentException("Specified invalid bind ip from property:DUBBO_IP_TO_BIND, value:" + hostToBind);
} else {
// host的获取顺序优先级 分别是 protocolConfig >> provider >> 本地网卡地址
if (StringUtils.isEmpty(hostToBind)) {
//protocolConfig 中获取
hostToBind = protocolConfig.getHost();
if (provider != null && StringUtils.isEmpty(hostToBind)) {
//provider中获取
hostToBind = provider.getHost();
}
if (NetUtils.isInvalidLocalHost(hostToBind)) {
anyhost = true;
if (logger.isDebugEnabled()) {
logger.info("No valid ip found from environment, try to get local host.");
}
//获取网卡ip
hostToBind = NetUtils.getLocalHost();
}
}
map.put("bind.ip", hostToBind);
String hostToRegistry = getValueFromConfig(protocolConfig, "DUBBO_IP_TO_REGISTRY");
if (StringUtils.isNotEmpty(hostToRegistry) && NetUtils.isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:DUBBO_IP_TO_REGISTRY, value:" + hostToRegistry);
} else {
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = hostToBind;
}
map.put("anyhost", String.valueOf(anyhost));
return hostToRegistry;
}
}
}
同理获取port也分别有三个源头 : 分别是 protocolConfig >> provider >> 扩展协议Protocol >> 本地随机可用 port
private static synchronized Integer findConfiguredPort(ProtocolConfig protocolConfig, ProviderConfig provider, ExtensionLoader<Protocol> extensionLoader, String name, Map<String, String> map) {
String port = getValueFromConfig(protocolConfig, "DUBBO_PORT_TO_BIND");
Integer portToBind = parsePort(port);
if (portToBind == null) {
portToBind = protocolConfig.getPort();
if (provider != null && (portToBind == null || portToBind == 0)) {
portToBind = provider.getPort();
}
//此处通过SPI可以扩展自定义的协议类型
int defaultPort = ((Protocol)extensionLoader.getExtension(name)).getDefaultPort();
if (portToBind == null || portToBind == 0) {
portToBind = defaultPort;
}
//获取一个随机可用的port
if (portToBind <= 0) {
portToBind = getRandomPort(name);
if (portToBind == null || portToBind < 0) {
portToBind = NetUtils.getAvailablePort(defaultPort);
putRandomPort(name, portToBind);
}
}
}
map.put("bind.port", String.valueOf(portToBind));
String portToRegistryStr = getValueFromConfig(protocolConfig, "DUBBO_PORT_TO_REGISTRY");
Integer portToRegistry = parsePort(portToRegistryStr);
if (portToRegistry == null) {
portToRegistry = portToBind;
}
return portToRegistry;
}
解决方案:
通过对上面的源码分析,可以得出结论:
1.protocolConfig 我们可以通过配置设置 host和port信息。
xml配置文件方式
<dubbo:protocol name=“dubbo” port=“20880” host=“192.168.1.8” accesslog=“true”/>
SpringBoot 注入方式
2.provider 直接设置host,port 默认采用 上面protocol设置的port。
xml配置文件方式
<dubbo:provider host=“192.168.1.8” protocol=“dubbo”/>
通过Api代码设置
ServiceConfig service = new ServiceConfig();否则可能造成内存和连接泄漏
service.setProtocol(protocol); // 多个协议可以用setProtocols()
service.setInterface(XxxService.class);
service.setRef(xxxService);
service.setVersion(“1.0.0”);