内容主旨
本篇文章主要介绍了如何进行自定义Dubbo服务对外暴露的主机地址的实战技术方案,其中我们需要针对于服务提供者侧的host主机暴漏的目的以及如何进行定制化处理
特性说明
在Dubbo中,Provider启动时主要做两个事情
- 启动服务提供者的server端实例。
- 服务自身向注册中心注册服务基本信息(服务IP地址、端口以及相关的一些服务实例级别的信息)。
启动server时需要绑定socket套接字以及监听对应的port端口,向注册中心注册自身服务时也是需要发送socket唯一标识服务地址。
开发Dubbo的阔扩展暴漏主机地址前需要的问题
-
Dubbo中不设置host时默认host是什么?
-
Dubbo中如何指定服务的host,我们是否可以用hostname或domain代替IP地址作为host?
-
使用docker时,有时需要设置端口映射,此时,启动server时绑定的socket和向注册中心注册的 socket 使用不同的端口号,此时又该如何设置?
使用场景
不设置host时,使用默认host,一般的dubbo协议配置如下:
xml配置方式
yaml配置方式
dubbo:
protocol:
name: dubbo
port: 20890
可以看到,按照上面的配置方式,我们只配置了端口号,没有配置host,此时设置的host是什么呢?
查看源码得知,在 org.apache.dubbo.config.ServiceConfig#findConfigedHosts() 方法获取host值。
/**
* Register & bind IP address for service provider, can be configured separately.
* Configuration priority: environment variables -> java system properties -> host property in config file ->
* /etc/hosts -> default network address -> first available network address
*
* @param protocolConfig
* @param registryURLs
* @param map
* @return
*/
private String findConfigedHosts(ProtocolConfig protocolConfig,
List<URL> registryURLs,
Map<String, String> map) {
boolean anyhost = false;
String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
}
// if bind ip is not found in environment, keep looking up
if (StringUtils.isEmpty(hostToBind)) {
hostToBind = protocolConfig.getHost();
if (provider != null && StringUtils.isEmpty(hostToBind)) {
hostToBind = provider.getHost();
}
if (isInvalidLocalHost(hostToBind)) {
anyhost = true;
try {
if (logger.isDebugEnabled()) {
logger.info("No valid ip found from environment, try to find valid host from DNS.");
}
hostToBind = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (isInvalidLocalHost(hostToBind)) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
// skip multicast registry since we cannot connect to it via Socket
continue;
}
try (Socket socket = new Socket()) {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
hostToBind = socket.getLocalAddress().getHostAddress();
break;
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (isInvalidLocalHost(hostToBind)) {
hostToBind = getLocalHost();
}
}
}
}
map.put(BIND_IP_KEY, hostToBind);
// registry ip is not used for bind ip by default
String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
} else if (StringUtils.isEmpty(hostToRegistry)) {
// bind ip is used as registry ip by default
hostToRegistry = hostToBind;
}
map.put(ANYHOST_KEY, String.valueOf(anyhost));
return hostToRegistry;
}
可以从源码看出来像关于读取host值的优先级,通过注释可以得知它的加载顺序如下。
Configuration priority: environment variables -> java system properties -> host property in config file ->
* /etc/hosts -> default network address -> first available network address
其中最主要还是依赖通过hostToBind = InetAddress.getLocalHost().getHostAddress();
获取默认host。
查找host的顺序
缺省主机IP查找顺序:
- 通过 LocalHost.getLocalHost() 获取本机地址。
- 如果是 127.* 等 loopback 地址,则扫描各网卡,获取网卡 IP。
主机配置
注册的地址如果获取不正确,比如需要注册公网地址,可以在 /etc/hosts 中加入:机器名 公网IP,比如:
www.xxx.com 205.182.23.114
在 dubbo.xml 中加入主机地址的配置:
或在 dubbo.properties 中加入主机地址的配置:端口配置
dubbo.protocol.host=205.182.23.201
application.yml文件对应的配置信息
dubbo:
protocol:
host: 205.182.23.201
其返回值如下:
- 未联网时,返回 127.0.0.1
- 在阿里云服务器中,返回私有地址,如: 172.18.46.234
- 在本机测试时,返回公有地址,如: 30.5.10.11
那在dubbo中如何指定服务的socket?
除此之外,可以通过dubbo.protocol或dubbo.provider的host属性对host进行配置,支持IP地址和域名,如下:
xml配置方式
yaml配置方式
dubbo:
protocol:
name: dubbo
port: 20890
host: www.xxx.com
开发Dubbo的阔扩展暴漏主机地址前需要的问题
在使用docker时,有时需要设置端口映射,此时,启动 server 时绑定的 socket 和向注册中心注册的 socket 使用不同的端口号,此时又该如何设置?
dubbo通过环境变量设置host
有些部署场景需要动态指定服务注册的地址,如docker bridge网络模式下要指定注册宿主机 ip 以实现外网通信。dubbo提供了两对启动阶段的系统属性,用于设置对外通信的ip、port地址。
- DUBBO_IP_TO_REGISTRY — 注册到注册中心的ip地址
- DUBBO_PORT_TO_REGISTRY — 注册到注册中心的port端口
- DUBBO_IP_TO_BIND — 监听ip地址
- DUBBO_PORT_TO_BIND — 监听port端口
以上四个配置项均为可选项,如不配置 dubbo会自动获取ip与端口,请根据具体的部署场景灵活选择配置。 dubbo支持多协议,如果一个应用同时暴露多个不同协议服务,且需要为每个服务单独指定ip或port,请分别在以上属性前加协议前缀。 如:
HESSIAN协议的定制化绑定
- HESSIAN_DUBBO_IP_TO_REGISTRY hessian协议注册的ip
- HESSIAN_DUBBO_PORT_TO_BIND hessian协议绑定的port
Dubbo协议的定制化绑定
- DUBBO_DUBBO_PORT_TO_BIND dubbo协议注册的ip
- DUBBO_DUBBO_PORT_TO_BIND dubbo协议绑定的port
PORT_TO_REGISTRY及IP_TO_REGISTRY与PORT_TO_BIND及IP_TO_BIND的关系
-
PORT_TO_REGISTRY或IP_TO_REGISTRY不会用作默认 PORT_TO_BIND 或 IP_TO_BIND,但是反过来是成立的
如设置 PORT_TO_REGISTRY=20881 IP_TO_REGISTRY=30.5.97.6,则 PORT_TO_BIND IP_TO_BIND 不受影响。 -
如果设置 PORT_TO_BIND=20881 IP_TO_BIND=30.5.97.6,则默认 PORT_TO_REGISTRY=20881 IP_TO_REGISTRY=30.5.97.6。
此外对应的缺省主机端口与协议相关:
总结
可以通过dubbo.protocol或dubbo.provider的host属性对host进行配置,支持IP地址和域名。但此时注册到注册中心的IP地址和监听IP地址是同一个值。
为了解决在虚拟环境或局域网内consumer无法与provider通信的问题,可以通过环境变量分别设置注册到注册中心的IP地址和监听IP地址,其优先级高于dubbo.protocol或dubbo.provider的host配置。
Docker的实战案例分析
Dockerfile文件
FROM openjdk:8-jdk-alpine
ADD target/dubbo-app.jar app.jar
ENV JAVA_OPTS=""
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar
docker build --no-cache -t app.
Create and run containers from mirroring
启动Zookeeper注册中心服务
docker run --name zkserver --restart always -d zookeeper:3.4.9
采用Docker的方式加入DUBBO_IP_TO_REGISTRY以及DUBBO_PORT_TO_REGISTRY的环境变量
docker run -e DUBBO_IP_TO_REGISTRY=30.5.97.6 -e DUBBO_PORT_TO_REGISTRY=20881 -p 30.5.97.6:20881:20880 --link zkserver:zkserver -it --rm dubbo-app
通过环境变量DUBBO_IP_TO_REGISTRY=30.5.97.6 和 DUBBO_PORT_TO_REGISTRY=20881设置提供程序以注册注册中心的IP地址和端口通过-p 30.5.97.6:20881:20880实现端口映射,其中20880是DUBBO自动选择的侦听端口。
没有监控IP配置,因此它将侦听0.0.0.0(所有IP)。启动后,提供程序的注册地址为30.5.97.6:20881,容器的侦听地址为:0.0.0.0:20880。
配置对应对比图(修改之前)
我们修改了对应的参数