Redisson-3.8 查找DNS异常的解决办法
背景
- Redisson 连接 redis 时出现 “At least two sentinels should be defined in Redis configuration!”
- 可能是因为配置的 sentinel 数量少于2个
- 可能是从其中一个 sentinel 上查找不到其他 sentinel 的信息
- 可能是尝试连接各个 sentinel 时,发现能连接上的数量少于2个
- 也可能是它使用的 netty 版本有 bug,在进行 DNS 解析时出现问题
- 如果本篇不能解决遇到的问题,可以参考另外一篇《 解决Redisson无法连接Sentinel, Netty查找DNS失败》
本文主要针对第5点进行分析和解决。
环境 (ubuntu)
- redis sentinel的链接url为
sentinel://redis:26379,redis:26380?masterNames=mymaster&poolSize=100&poolName=xxx
- /etc/resolv.conf的内容如下:
nameserver 127.0.0.1search aaa.bbb ostechnix.lan
(在这里,aaa.bbb是解析不了的)
- ping 和 nslookup redis:
$ ping redis
PING redis.ostechnix.lan (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.026 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.057 ms$ nslookup redis
Server: 127.0.0.1
Address: 127.0.0.1#53Name: redis.ostechnix.lan
Address: 127.0.0.1
- 使用redis-cli可以连接redis的sentinel:
$ src/redis-cli -h redis -p 26379
redis:26379> sentinel sentinels mymaster
1) 1) "name"2) "127.0.0.1:26380"3) "ip"4) "127.0.0.1"5) "port"6) "26380"...$ src/redis-cli -h redis -p 26380
1) 1) "name"2) "127.0.0.1:26379"3) "ip"4) "127.0.0.1"5) "port"6) "26379"
现象
java程序使用redisson-3.8.2尝试连接redis,出现了错误
Exception in thread "main" org.redisson.client.RedisConnectionException: At least two sentinels should be defined in Redis configuration!at org.redisson.connection.SentinelConnectionManager.<init>(SentinelConnectionManager.java:159)at org.redisson.config.ConfigSupport.createConnectionManager(ConfigSupport.java:195)at org.redisson.Redisson.<init>(Redisson.java:122)at org.redisson.Redisson.create(Redisson.java:161)...
打开redisson的debug日志:
2018-11-21 18:11:53 [main] WARN o.r.c.SentinelConnectionManager - Can't connect to sentinel server. Unable to connect to: redis://redis:26379
...
2018-11-21 18:12:03 [main] WARN o.r.c.SentinelConnectionManager - Can't connect to sentinel server. Unable to connect to: redis://redis:26380
Well, 为何 redis-cli 能连接得上sentinel,而java程序会出错?java程序在使用以前的版本redisson-2.5.1的时候是一切正常的。
重新看一次debug日志,发现了一个奇怪的地方:
2018-11-21 18:11:48 [main] DEBUG i.netty.resolver.dns.DnsQueryContext - [id: 0xda4ab0e7] WRITE: [49889: /127.0.0.1:53], DefaultDnsQuestion(redis.aaa.bbb ostechnix.lan. IN A)
2018-11-21 18:11:48 [main] DEBUG i.netty.resolver.dns.DnsQueryContext - [id: 0xda4ab0e7] WRITE: [34575: /127.0.0.1:53], DefaultDnsQuestion(redis.aaa.bbb ostechnix.lan. IN AAAA)
解释一下:
IN A:代表主机名到 IPv4 地址的映射
IN AAAA:代表主机名到 IPv6 地址的映射
“ostechnix.lan.” 最后的点,代表根,“lan.” 表示lan为根下的第一级域
再来看这个域名 “redis.aaa.bbb ostechnix.lan.”,有点奇怪,域名中间为何会出现空格?
回头看/etc/resolv.conf文件,发现
search aaa.bbb ostechnix.lan
解释一下:
search:各项间以空格或者tab分隔,当域名没有以点结尾时,需要从这里追加各项,作为完全限定域名再发送DNS请求。
很明显,在解析search项的时候,没有用空格分隔开各项,导致DNS请求的域名存在错误。
查找出错的地方
查看redisson的源码,发现RedisClient的resolvAddr方法会对地址进行解析,如果/etc/resolv.conf里面存在多个DNS server的配置,会给每个配置都建立一个DnsNameResolver(这部分是属于netty-4.1.30.Final的源码)。
查看DnsNameResolver的源码:
static {String[] searchDomains;try {List<String> list = PlatformDependent.isWindows()? getSearchDomainsHack(): UnixResolverDnsServerAddressStreamProvider.parseEtcResolverSearchDomains();searchDomains = list.toArray(new String[0]);} catch (Exception ignore) {// Failed to get the system name search domain list.searchDomains = EmptyArrays.EMPTY_STRINGS;}DEFAULT_SEARCH_DOMAINS = searchDomains;
...}
searchDomains 是通过UnixResolverDnsServerAddressStreamProvider.parseEtcResolverSearchDomains() 来解析的
查看该方法源码:
static List<String> parseEtcResolverSearchDomains(File etcResolvConf) throws IOException {String localDomain = null;List<String> searchDomains = new ArrayList<String>();FileReader fr = new FileReader(etcResolvConf);BufferedReader br = null;try {br = new BufferedReader(fr);String line;while ((line = br.readLine()) != null) {if (localDomain == null && line.startsWith(DOMAIN_ROW_LABEL)) {int i = indexOfNonWhiteSpace(line, DOMAIN_ROW_LABEL.length());if (i >= 0) {localDomain = line.substring(i);}} else if (line.startsWith(SEARCH_ROW_LABEL)) {int i = indexOfNonWhiteSpace(line, SEARCH_ROW_LABEL.length());if (i >= 0) {searchDomains.add(line.substring(i));}}}} finally {if (br == null) {fr.close();} else {br.close();}}
Well,看来是netty对于search的解析有了新的想法,认为search是每项一行,所以木有再对每行进行空格或者tab的切割
searchDomains.add(line.substring(i));
解决办法
方法1:
把 UnixResolverDnsServerAddressStreamProvider 的源码 copy 到应用中,按照相同的package路径放置,然后修改 parseEtcResolverSearchDomains 方法,对每行进行split
方法2:
修改/etc/resolv.conf文件,把search改成每行一项,譬如:
nameserver 127.0.0.1search aaa.bbb
search ostechnix.lan
参考
- Introduction to the Domain Name System (DNS)
- Build your own DNS server on Linux (适用于redhat,centos)
- Install and configure DNS server in Ubuntu 16.04 LTS (适用于ubuntu)
PS:
- 在ubuntu上,/etc/resolv.conf 文件不能直接修改(会被定时重写),需要通过修改目录 /etc/resolvconf/resolv.conf.d/ 下的base, head, tail文件来完成,具体可以通过"man resolvconf"来查看相关说明,修改后使用命令“sudo resolvconf -u”可以马上复写/etc/resolv.conf 文件
- 在某些版本的ubuntu上,修改base文件是不会生效的,需要修改head文件来实现(虽然head文件说不要edit它,但是真的没办法)
Something Important !!!
如果你自己搭建了DNS server来模拟上述的实验,有可能还是出错,说连接不了sentinel。
以实验为例,每个DnsNameResolver在上述改动后,都会拿到2个domain(aaa.bbb 和 ostechnix.lan)。
你可以尝试改变这2个domain的顺序,譬如:
search ostechnix.lan aaa.bbb
或者
search ostechnix.lan
search aaa.bbb
再一次实验,就会发现这次竟然通过了。
Well,是不是很神奇?
调试一下 DnsResolveContext的 resolve方法
searchDomainPromise.addListener(new FutureListener<List<T>>() {private int searchDomainIdx = initialSearchDomainIdx;@Overridepublic void operationComplete(Future<List<T>> future) {Throwable cause = future.cause();if (cause == null) {promise.trySuccess(future.getNow());} else {if (DnsNameResolver.isTransportOrTimeoutError(cause)) {promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname));} else if (searchDomainIdx < searchDomains.length) {Promise<List<T>> newPromise = parent.executor().newPromise();newPromise.addListener(this);doSearchDomainQuery(hostname + '.' + searchDomains[searchDomainIdx++], newPromise);} else if (!startWithoutSearchDomain) {internalResolve(hostname, promise);} else {promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname));}}}});
顺带说一下,这个方法会一个一个searchDomain的去尝试。
着重调试:
promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname));
你会发现,当发送DNS请求(redis.aaa.bbb)到自己的DNS时,会出现5000ms的超时错误。
可以在命令行进行尝试:
$ nslookup redis.aaa.bbb 127.0.0.1
Server: 127.0.0.1
Address: 127.0.0.1#53** server can't find redis.aaa.bbb: SERVFAIL
忽略这个错误,注意测量一下耗时,是不是早就过了5s?
结合上面的代码,当出现timeout错误时,下一个domain是不会继续去连接的,所以当顺序为"[aaa.bbb, ostechnix.lan]"时,程序一样报错。
那这个5000ms的限制是哪里加入的咧?
再次调试源码,发现DnsNameResolver是由DnsNameResolverBuilder来构造的
public final class DnsNameResolverBuilder {
...private long queryTimeoutMillis = 5000;
...
解决办法
copy源码到应用目录,改一下这个数字,完事
Redisson-3.8 查找DNS异常的解决办法相关推荐
- Android 常见异常及解决办法
Ø 前言 本文主要记录 Android 的常见异常及解决办法,以备以后遇到相同问题时可以快速解决. 1. java.lang.NullPointerException: Attempt to i ...
- JNI调用native方法出现 java.lang.UnsatisfiedLinkError: XXXclass.XXXmethod()异常的解决办法
JNI调用native方法出现 java.lang.UnsatisfiedLinkError: XXXclass.XXXmethod()异常的解决办法 参考文章: (1)JNI调用native方法出现 ...
- Delphi的“Invalid pointer operation“异常的解决办法
Delphi的"Invalid pointer operation"异常的解决办法 参考文章: (1)Delphi的"Invalid pointer operation& ...
- 阿里云服务器安全组配置-有关访问实例异常的解决办法
阿里云服务器安全组配置-有关访问实例异常的解决办法 参考文章: (1)阿里云服务器安全组配置-有关访问实例异常的解决办法 (2)https://www.cnblogs.com/ylcxBlog/p/7 ...
- Android Stduio 发生 Process ‘command ‘somePath:java.exe‘‘ finished with non-zero exit value 2 异常的解决办法
Android Stduio 发生 Process 'command 'somePath:java.exe'' finished with non-zero exit value 2 异常的解决办法 ...
- nested exception is java.lang.NoClassDefFoundError: org/codehaus/jettison/json/JSONObject异常的解决办法
nested exception is java.lang.NoClassDefFoundError: org/codehaus/jettison/json/JSONObject异常的解决办法 参考文 ...
- IIS 常见异常及解决办法
Ø 简介 IIS 是我们平常接触比较多的服务端软件,用于站点发布等,本文主要记录 IIS 常见的异常及解决办法.主要包括: 1. Visual Studio 启动 Web 项目提示"无 ...
- 基于ABP的应用执行Update-Database出现异常的解决办法
基于ABP的应用执行Update-Database出现异常的解决办法 参考文章: (1)基于ABP的应用执行Update-Database出现异常的解决办法 (2)https://www.cnblog ...
- uni-app开发 常见异常和解决办法
文章目录 前言 1.调用微信开发者工具报错IDE service port disabled 2.@import导入外部样式失效 3.v-for列表渲染指定`:key`属性报错Duplicate ke ...
最新文章
- 第二十八课.AlphaGo实例分析
- Fast Terrain Rendering Using Geometrical MipMappin
- Echarts使用笔记
- 用VC写Assembly代码(4)
- esd文件转换成gho文件_STL到OBJ:如何将STL文件转换成OBJ ?
- [数据分析工具] Pandas 不可不知的功能(一)
- Android中Activity出现与退出的自定义动画
- vue 列表 萌层 鼠标移入移出_10 分钟上手 Vue 组件 Vue-Draggable
- 树莓派保持网络连接shell脚本
- 计算机组成与系统结构概述
- 推荐个工作日志的软件nyfedit
- 微信小程序保存图片到相册
- AWTK-MVVM 在 STM32H743 上的移植笔记
- 软件架构设计师:软件架构风格
- 电商搜索“优化商品排序”实践方案
- 阿里云公布 “抄袭事件” 调查结果:确有员工违规
- [转]数据的游戏:冰与火
- MAX() OVER() 函数
- 从苏宁电器到卡巴斯基第24篇:单证这一年(下)
- shell脚本 把十进制数据转换成十六进制
热门文章
- 物理机上架设云服务器
- 李子柒事件:微念究竟“扮演”的是什么角色
- CAD安装后无法打开,提示错误。怎么办?
- idea mac 查找文件快捷键_IntelliJ IDEA For Mac 快捷键
- Windows 7的网络发现不能启用
- 全球最具人气网站地图
- 银行虚拟化部署设计方案
- 五子棋终结者的算法求解过程
- 【架构 Flutter实践 Clean架构 TDD测试驱动开发---1.0】
- php 数组 json_decode,php中json_decode返回数组或对象_PHP教程