背景

  1. Redisson 连接 redis 时出现 “At least two sentinels should be defined in Redis configuration!”
  2. 可能是因为配置的 sentinel 数量少于2个
  3. 可能是从其中一个 sentinel 上查找不到其他 sentinel 的信息
  4. 可能是尝试连接各个 sentinel 时,发现能连接上的数量少于2个
  5. 也可能是它使用的 netty 版本有 bug,在进行 DNS 解析时出现问题
  6. 如果本篇不能解决遇到的问题,可以参考另外一篇《 解决Redisson无法连接Sentinel, Netty查找DNS失败》

本文主要针对第5点进行分析和解决。

环境 (ubuntu)

  1. redis sentinel的链接url为
sentinel://redis:26379,redis:26380?masterNames=mymaster&poolSize=100&poolName=xxx
  1. /etc/resolv.conf的内容如下:
nameserver 127.0.0.1search aaa.bbb ostechnix.lan

(在这里,aaa.bbb是解析不了的)

  1. 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
  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

参考

  1. Introduction to the Domain Name System (DNS)
  2. Build your own DNS server on Linux (适用于redhat,centos)
  3. Install and configure DNS server in Ubuntu 16.04 LTS (适用于ubuntu)

PS:

  1. 在ubuntu上,/etc/resolv.conf 文件不能直接修改(会被定时重写),需要通过修改目录 /etc/resolvconf/resolv.conf.d/ 下的base, head, tail文件来完成,具体可以通过"man resolvconf"来查看相关说明,修改后使用命令“sudo resolvconf -u”可以马上复写/etc/resolv.conf 文件
  2. 在某些版本的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异常的解决办法相关推荐

  1. Android 常见异常及解决办法

    Ø  前言 本文主要记录 Android 的常见异常及解决办法,以备以后遇到相同问题时可以快速解决. 1.   java.lang.NullPointerException: Attempt to i ...

  2. JNI调用native方法出现 java.lang.UnsatisfiedLinkError: XXXclass.XXXmethod()异常的解决办法

    JNI调用native方法出现 java.lang.UnsatisfiedLinkError: XXXclass.XXXmethod()异常的解决办法 参考文章: (1)JNI调用native方法出现 ...

  3. Delphi的“Invalid pointer operation“异常的解决办法

    Delphi的"Invalid pointer operation"异常的解决办法 参考文章: (1)Delphi的"Invalid pointer operation& ...

  4. 阿里云服务器安全组配置-有关访问实例异常的解决办法

    阿里云服务器安全组配置-有关访问实例异常的解决办法 参考文章: (1)阿里云服务器安全组配置-有关访问实例异常的解决办法 (2)https://www.cnblogs.com/ylcxBlog/p/7 ...

  5. 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 异常的解决办法 ...

  6. nested exception is java.lang.NoClassDefFoundError: org/codehaus/jettison/json/JSONObject异常的解决办法

    nested exception is java.lang.NoClassDefFoundError: org/codehaus/jettison/json/JSONObject异常的解决办法 参考文 ...

  7. IIS 常见异常及解决办法

    Ø  简介 IIS 是我们平常接触比较多的服务端软件,用于站点发布等,本文主要记录 IIS 常见的异常及解决办法.主要包括: 1.   Visual Studio 启动 Web 项目提示"无 ...

  8. 基于ABP的应用执行Update-Database出现异常的解决办法

    基于ABP的应用执行Update-Database出现异常的解决办法 参考文章: (1)基于ABP的应用执行Update-Database出现异常的解决办法 (2)https://www.cnblog ...

  9. uni-app开发 常见异常和解决办法

    文章目录 前言 1.调用微信开发者工具报错IDE service port disabled 2.@import导入外部样式失效 3.v-for列表渲染指定`:key`属性报错Duplicate ke ...

最新文章

  1. 第二十八课.AlphaGo实例分析
  2. Fast Terrain Rendering Using Geometrical MipMappin
  3. Echarts使用笔记
  4. 用VC写Assembly代码(4)
  5. esd文件转换成gho文件_STL到OBJ:如何将STL文件转换成OBJ ?
  6. [数据分析工具] Pandas 不可不知的功能(一)
  7. Android中Activity出现与退出的自定义动画
  8. vue 列表 萌层 鼠标移入移出_10 分钟上手 Vue 组件 Vue-Draggable
  9. 树莓派保持网络连接shell脚本
  10. 计算机组成与系统结构概述
  11. 推荐个工作日志的软件nyfedit
  12. 微信小程序保存图片到相册
  13. AWTK-MVVM 在 STM32H743 上的移植笔记
  14. 软件架构设计师:软件架构风格
  15. 电商搜索“优化商品排序”实践方案
  16. 阿里云公布 “抄袭事件” 调查结果:确有员工违规
  17. [转]数据的游戏:冰与火
  18. MAX() OVER() 函数
  19. 从苏宁电器到卡巴斯基第24篇:单证这一年(下)
  20. shell脚本 把十进制数据转换成十六进制

热门文章

  1. 物理机上架设云服务器
  2. 李子柒事件:微念究竟“扮演”的是什么角色
  3. CAD安装后无法打开,提示错误。怎么办?
  4. idea mac 查找文件快捷键_IntelliJ IDEA For Mac 快捷键
  5. Windows 7的网络发现不能启用
  6. 全球最具人气网站地图
  7. 银行虚拟化部署设计方案
  8. 五子棋终结者的算法求解过程
  9. 【架构 Flutter实践 Clean架构 TDD测试驱动开发---1.0】
  10. php 数组 json_decode,php中json_decode返回数组或对象_PHP教程