最近在生产环境碰到过多次域名解析失败的问题,有时候还是客户windowns环境报障,是时候深入了解下Vertx内部的域名解析机制了。

1、Vertx使用DNS方法
import java.util.Arrays;

import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.dns.AddressResolverOptions;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpClientRequest;

public class DemoMain {
public static void main(String[] args) throws Exception {
AddressResolverOptions addressResolverOptions = new AddressResolverOptions();
addressResolverOptions.setNdots(1);
addressResolverOptions.setServers(Arrays.asList("8.8.8.8"));
addressResolverOptions.setSearchDomains(Arrays.asList(".com"));

VertxOptions vertxOptions = new VertxOptions();
vertxOptions.setAddressResolverOptions(addressResolverOptions);
Vertx vertx = Vertx.vertx(vertxOptions);
HttpClientOptions clientOp = new HttpClientOptions();
clientOp.setSsl(true);
clientOp.setTrustAll(true);
clientOp.setVerifyHost(false);
HttpClient httpClient = vertx.createHttpClient(clientOp);
HttpClientRequest req = httpClient.get(443, "www.baidu.com", "/index.html", resp -> {System.out.println(resp.statusCode());vertx.close();
});
req.end();

}
}
AddressResolverOptions 有几个重要属性
servers: 8.8.8.8,8.8.4.4 #对应Linux /etc/resolv.conf的nameserver,DNS服务器地址,支持配置多个,以逗号隔开
ndots: 1 #对应linux /etc/resolv.conf里面的options: ndots, 作用就是如果给的域名里面包含的点的个数少于该阈值,那么DNS解析的时候就会默认加上searchDomains的值,这个必须和searchDomains搭配使用,Linux默认为1,华为公有云PAAS(包含容器)默认是4
searchDomains: a,b,c #对应linux /etc/resolv.conf里面的search,和ndots搭配使用,如果当前域名的点个数少于设置值,解析时就会把这些值添加到域名后面一起解析,比如ndots设置的为4,当前域名为servicecomb.cn-north-1.myhwclouds.com,只有三个点,那么解析的时候就会自动加上servicecomb.cn-north-1.myhwclouds.com.a去解析,没解析出来在用servicecomb.cn-north-1.myhwclouds.com.b,直到能最后解析出来
optResourceEnabled: true #optional record is automatically included in DNS queries
cacheMinTimeToLive: 0 #最小缓存时间
cacheMaxTimeToLive: 10000 #最大缓存时间
cacheNegativeTimeToLive: 0 #DNS解析失败后,下次重试的等待时间
queryTimeout: 5000 #查询超时时间
maxQueries: 4 #查询次数
rdFlag: true #设置DNS递归查询
rotateServers: true #设置是否支持轮询,如果有多个域名服务器,轮训可以加快域名解析速度

2、代码解析过程
使用Vertx vertx = Vertx.vertx(vertxOptions),在VertxImpl构造方法里面会初始化this.addressResolver = new AddressResolver(this, options.getAddressResolverOptions());
2.1 io.vertx.core.impl.AddressResolver里面有个static块,会读取/etc/resolv.conf文件,解析得到ndots和rotate默认值,在构造方法内初始化
public AddressResolver(Vertx vertx, AddressResolverOptions options) {
this.provider = ResolverProvider.factory(vertx, options);
this.resolverGroup = provider.resolver(options);
this.vertx = vertx;
}

ResolverProvider获取provider的时候,默认得到DnsResolverProvider,这个是Vertx提供的默认域名解析。

2.2 DnsResolverProvider代码解析

public DnsResolverProvider(VertxImpl vertx, AddressResolverOptions options) {
List<String> dnsServers = options.getServers();
List<InetSocketAddress> serverList = new ArrayList<>();
// 看是否有配置dns服务地址,如果配置了则使用配置的地址
if (dnsServers != null && dnsServers.size() > 0) {
for (String dnsServer : dnsServers) {
// 解析域名地址和端口,默认是53
int sep = dnsServer.indexOf(':');
String ipAddress;
int port;
if (sep != -1) {
ipAddress = dnsServer.substring(0, sep);
port = Integer.parseInt(dnsServer.substring(sep + 1));
} else {
ipAddress = dnsServer;
port = 53;
}
try {
// 检查地址是否正确
serverList.add(new InetSocketAddress(InetAddress.getByAddress(NetUtil.createByteArrayFromIpAddressString(ipAddress)), port));
} catch (UnknownHostException e) {
throw new VertxException(e);
}
}
} else {
// 如果没有配置域名地址,则需要读取服务器上的地址
// 这里直接使用了Netty提供的获取服务器上默认域名地址,这里最终会调用DefaultDnsServerAddressStreamProvider,详细参考2.3
DnsServerAddressStream stream = DnsServerAddresses.defaultAddresses().stream();
Set<InetSocketAddress> all = new HashSet<>();
while (true) {
InetSocketAddress address = stream.next();
if (all.contains(address)) {
break;
}
serverList.add(address);
all.add(address);
}
}
DnsServerAddresses nameServerAddresses = options.isRotateServers() ? DnsServerAddresses.rotational(serverList) : DnsServerAddresses.sequential(serverList);
DnsServerAddressStreamProvider nameServerAddressProvider = hostname -> nameServerAddresses.stream();

// 解析host文件,不需要通过域名服务器解析,比如/etc/hosts,支持配置文件路径和直接设置。如果都为空,则读取系统的默认配置,windows读取C:\Windows\System32\drivers\etc\hosts,Linux读取/etc/hosts
HostsFileEntries entries;
if (options.getHostsPath() != null) {
File file = vertx.resolveFile(options.getHostsPath()).getAbsoluteFile();
try {
if (!file.exists() || !file.isFile()) {
throw new IOException();
}
entries = HostsFileParser.parse(file);
} catch (IOException e) {
throw new VertxException("Cannot read hosts file " + file.getAbsolutePath());
}
} else if (options.getHostsValue() != null) {
try {
entries = HostsFileParser.parse(new StringReader(options.getHostsValue().toString()));
} catch (IOException e) {
throw new VertxException("Cannot read hosts config ", e);
}
} else {
entries = HostsFileParser.parseSilently();
}

int minTtl = intValue(options.getCacheMinTimeToLive(), 0);
int maxTtl = intValue(options.getCacheMaxTimeToLive(), Integer.MAX_VALUE);
int negativeTtl = intValue(options.getCacheNegativeTimeToLive(), 0);
DnsCache resolveCache = new DefaultDnsCache(minTtl, maxTtl, negativeTtl);
DnsCache authoritativeDnsServerCache = new DefaultDnsCache(minTtl, maxTtl, negativeTtl);this.vertx = vertx;
// 初始化AddressResolverGroup
this.resolverGroup = new AddressResolverGroup<InetSocketAddress>() {@Overrideprotected io.netty.resolver.AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) throws Exception {ChannelFactory<DatagramChannel> channelFactory = () -> vertx.transport().datagramChannel();DnsAddressResolverGroup group = new DnsAddressResolverGroup(channelFactory, nameServerAddressProvider) {@Overrideprotected NameResolver<InetAddress> newNameResolver(EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory, DnsServerAddressStreamProvider nameServerProvider) throws Exception {DnsNameResolverBuilder builder = new DnsNameResolverBuilder((EventLoop) executor);builder.hostsFileEntriesResolver(new HostsFileEntriesResolver() {@Overridepublic InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {InetAddress address = lookup(inetHost, resolvedAddressTypes);if (address == null) {address = lookup(inetHost.toLowerCase(Locale.ENGLISH), resolvedAddressTypes);}return address;}InetAddress lookup(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {switch (resolvedAddressTypes) {case IPV4_ONLY:return entries.inet4Entries().get(inetHost);case IPV6_ONLY:return entries.inet6Entries().get(inetHost);case IPV4_PREFERRED:Inet4Address inet4Address = entries.inet4Entries().get(inetHost);return inet4Address != null? inet4Address : entries.inet6Entries().get(inetHost);case IPV6_PREFERRED:Inet6Address inet6Address = entries.inet6Entries().get(inetHost);return inet6Address != null? inet6Address : entries.inet4Entries().get(inetHost);default:throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);}}});builder.channelFactory(channelFactory);builder.nameServerProvider(nameServerAddressProvider);builder.optResourceEnabled(options.isOptResourceEnabled());builder.resolveCache(resolveCache);builder.authoritativeDnsServerCache(authoritativeDnsServerCache);builder.queryTimeoutMillis(options.getQueryTimeout());builder.maxQueriesPerResolve(options.getMaxQueries());builder.recursionDesired(options.getRdFlag());// 必须配置了searchDomains,ndots才起作用if (options.getSearchDomains() != null) {builder.searchDomains(options.getSearchDomains());int ndots = options.getNdots();if (ndots == -1) {ndots = AddressResolver.DEFAULT_NDOTS_RESOLV_OPTION;}builder.ndots(ndots);}return builder.build();}};io.netty.resolver.AddressResolver<InetSocketAddress> resolver = group.getResolver(executor);resolvers.add(new ResolverRegistration(resolver, (EventLoop) executor));return resolver;}
};

}

2.3 DefaultDnsServerAddressStreamProvider 读取系统默认域名解析地址,使用static块,利用jndi-DNS获取域名地址列表

static {
final List<InetSocketAddress> defaultNameServers = new ArrayList<InetSocketAddress>(2);

    // Using jndi-dns to obtain the default name servers.//// See:// - http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-dns.html// - http://mail.openjdk.java.net/pipermail/net-dev/2017-March/010695.htmlHashtable<String, String> env = new Hashtable<String, String>();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");env.put("java.naming.provider.url", "dns://");try {DirContext ctx = new InitialDirContext(env);// 获取dnsUrl地址String dnsUrls = (String) ctx.getEnvironment().get("java.naming.provider.url");// Only try if not empty as otherwise we will produce an exceptionif (dnsUrls != null && !dnsUrls.isEmpty()) {String[] servers = dnsUrls.split(" ");for (String server : servers) {try {URI uri = new URI(server);String host = new URI(server).getHost();if (host == null || host.isEmpty()) {logger.debug("Skipping a nameserver URI as host portion could not be extracted: {}", server);// If the host portion can not be parsed we should just skip this entry.continue;}int port  = uri.getPort();defaultNameServers.add(SocketUtils.socketAddress(uri.getHost(), port == -1 ? DNS_PORT : port));} catch (URISyntaxException e) {logger.debug("Skipping a malformed nameserver URI: {}", server, e);}}}} catch (NamingException ignore) {// Will try reflection if this fails.}if (defaultNameServers.isEmpty()) {try {Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");Method open = configClass.getMethod("open");Method nameservers = configClass.getMethod("nameservers");Object instance = open.invoke(null);@SuppressWarnings("unchecked")final List<String> list = (List<String>) nameservers.invoke(instance);for (String a: list) {if (a != null) {defaultNameServers.add(new InetSocketAddress(SocketUtils.addressByName(a), DNS_PORT));}}} catch (Exception ignore) {// Failed to get the system name server list via reflection.// Will add the default name servers afterwards.}}if (!defaultNameServers.isEmpty()) {if (logger.isDebugEnabled()) {logger.debug("Default DNS servers: {} (sun.net.dns.ResolverConfiguration)", defaultNameServers);}} else {// Depending if IPv6 or IPv4 is used choose the correct DNS servers provided by google:// https://developers.google.com/speed/public-dns/docs/using// https://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.htmlif (NetUtil.isIpV6AddressesPreferred() ||(NetUtil.LOCALHOST instanceof Inet6Address && !NetUtil.isIpV4StackPreferred())) {Collections.addAll(defaultNameServers,SocketUtils.socketAddress("2001:4860:4860::8888", DNS_PORT),SocketUtils.socketAddress("2001:4860:4860::8844", DNS_PORT));} else {Collections.addAll(defaultNameServers,SocketUtils.socketAddress("8.8.8.8", DNS_PORT),SocketUtils.socketAddress("8.8.4.4", DNS_PORT));}if (logger.isWarnEnabled()) {logger.warn("Default DNS servers: {} (Google Public DNS as a fallback)", defaultNameServers);}}DEFAULT_NAME_SERVER_LIST = Collections.unmodifiableList(defaultNameServers);DEFAULT_NAME_SERVER_ARRAY = defaultNameServers.toArray(new InetSocketAddress[defaultNameServers.size()]);DEFAULT_NAME_SERVERS = sequential(DEFAULT_NAME_SERVER_ARRAY);
}

3、潜在的问题
3.1 jndi-DNS获取的域名解析机制,底层使用native code方法,具体实现不详。猜测Linux是直接获取/etc/resolv.conf里面配置的地址,windows获取网卡配置的域名地址。在windows下面,如果有多个网卡,并且有网卡是不通外网的。

3.2 DNS默认的searchDomain没有配置,也没有获取系统的配置,单独设置ndots是不生效的。

Vertx DNS解析源码相关推荐

  1. Qt源码解析-源码解析-QVideoWidget播放手机视频旋转问题

    Qt源码解析 索引 Qt源码解析-源码解析-QVideoWidget播放手机视频旋转问题 问题描述与复现 使用手机拍摄的视频是竖屏的,上传后,使用QVideoWidget播放,变成横屏. 总结,可以让 ...

  2. php获取腾讯视频信息,云水日记-PHP实现腾讯视频解析源码

    PHP实现腾讯视频解析源码,只能解析非VIP的视频,否则只能看3分钟,仅供考学习使用....<?php //腾讯视频解析 $vids = isset($_GET['vids']) ? $_GET ...

  3. php文件直链源码,蓝奏网盘文件夹直链解析源码

    蓝奏网盘文件夹直链解析源码 @晶晶易.版本 2 .支持库 spec .程序集 程序集1 .子程序 _启动子程序, 整数型, , 本子程序在程序启动后最先执行 .局部变量 返回文本, 文本型 .局部变量 ...

  4. 二次解析源码全kyuan

    介绍: 紫胤二次解析源码纯kyuan 没有什么好说的,需要的下载吧 网盘下载地址: http://kekewangLuo.net/JCGIOeZfV0B 图片:

  5. yy神曲url解析php_歪歪神曲解析源码(参考)

    歪歪神曲解析源码(参考) 2018-12-4 阅读:1589 只提供参考,自行测试更多资源交流群516722628 //$url = "http://www.yy.com/x/15012_1 ...

  6. 【SpringBoot】最新版2019Spring Boot配置解析,源码解析(速成SpringBoot)——学习笔记版【2】

    SpringBoot配置文件 文章目录 SpringBoot配置文件 四.配置文件 1.简介 2.YAML用法 2.1 简介 2.2语法 3.为属性注入值 3.1使用.yml配置文件 3.1编写.ym ...

  7. 一款非常漂亮影视在线解析源码后台

    介绍 一款非常漂亮的解析源码,可直接前端输入链接解析,可自用可当作 二次解析或影视站接口,接口格式:你的域名/?url= 后台自带解析接口, 超清无广告,支持超前点播,如失效可在后台自行更改,无需安装 ...

  8. 【JAVA秘籍心法篇-Spring】Spring XML解析源码详解

    [JAVA秘籍心法篇-Spring]Spring XML解析源码详解 所谓天下武功,无坚不摧,唯快不破.但有又太极拳法以快制慢,以柔克刚.武功外式有拳打脚踢,刀剑棍棒,又有内功易筋经九阳神功.所有外功 ...

  9. PHP短视频聚合无水印在线解析源码

    PHP短视频聚合无水印在线解析源码,一键解析,方便快捷 接口是作者那边的API,不保证时效性! 有技术的朋友可以更换成自己的,将复制的分享链接粘贴到输入框即可进行解析. 链接: https://pan ...

最新文章

  1. select2的一些特性效果
  2. SpringMVC运行流程分析
  3. view渐变色,透明度渐变
  4. matlab如何画一个平面,matlab 画平面
  5. 疯狂java workflow_疯狂Workflow讲义:基于Activiti的工作流应用开发 完整pdf扫描版[136MB]...
  6. lua html 转义字符,Lua中特殊字符过滤(UTF8编码)
  7. 《机器学习》(周志华)笔记
  8. 《实施Cisco统一通信管理器(CIPT1)》一1.3 总结
  9. windows文件自动同步
  10. 代码整洁之道 php,关于代码整洁之道的详细介绍
  11. linux网络配置文件,Linux下一块网卡设置多个IP地址
  12. 苹果手机从企业微信进入小程序下载不了图片
  13. JavaWeb程序设计课后答案
  14. pca图解读_PCA 图像识别 详解(一)
  15. 最强大脑记忆曲线(12)-- 录入数据修改
  16. 研招考试科目计算机专业课408代表什么?
  17. 174. Dungeon Game(H)
  18. 弘辽科技:淘宝店铺怎么优化商品关键词?
  19. 关于YDWE在保存时的Lua数据添加
  20. 怎样对股票交易下单接口进行测试?

热门文章

  1. Java 视频截取帧数保存为本地图片及删除本地图片
  2. ssm+jsp计算机毕业设计疫情期间北张村村民行程管理系统mwj1v(程序+lw+源码+远程部署)
  3. Ubuntu20版-阿里源
  4. RAID——磁盘阵列
  5. 它的出现将统一所有浏览器存储 API ?!
  6. linux查看wifi信号命令_如何用wifi-linux检测AP信号强度
  7. MySQL 中用NOW()和SYSDATE()函数获取当前日期时间
  8. 开发者收益路径—广告变现,App商业化如何有效增收?
  9. Notes: harddisk performance testing
  10. 用 Python 远程控制 Windows 服务器,简直太方便了~