JDK 的InetSocketAddress
被设计了为一个不可变的对象,在Socket
等长连接使用时需要注意DNS映射关系更新问题。
问题描述 最近遇到一个zookeeper-client 更新DNS映射关系未生效问题,记录一下该问题的原因。
域名映射关系:
1 127.0.0.1 test.lan-gl-42.dev
java应用程序
1 -Dsun.net.inetaddr.ttl=15
Java Socket Client Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testSocket () { String host = "test.lan-gl-42.dev" ; int port = 9876 ; try (Socket socket = new Socket (host, port); ObjectOutputStream oos = new ObjectOutputStream (socket.getOutputStream())) { System.out.println("Sending request to Socket Server,socket remote:" + socket.getRemoteSocketAddress().toString()); while (true ){ Thread.sleep(10000 ); System.out.println("Sending request to Socket Server,socket remote:" + socket.getRemoteSocketAddress().toString()); } } catch (IOException | InterruptedException e) { e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } }
执行日志:
1 2 3 Sending request to Socket Server,socket remote:test.lan-gl-42.dev/127.0.0.1:9876 Sending request to Socket Server,socket remote:test.lan-gl-42.dev/127.0.0.1:9876 Sending request to Socket Server,socket remote:test.lan-gl-42.dev/127.0.0.1:9876
修改域名的映射关系为:
1 10.224.17.250 test.lan-gl-42.dev
** socket连接的地址依然是 127.0.0.1 **
原因 Java进程及OS DNS解析过程:
图片来源
JVM会缓存DNS name lookups,它在指定的时间段内缓存IP地址,称为生存时间(time-to-live, TTL)
因为InetSocketAddress
被设计了为一个不可变的对象(immutable object ),Socket
对象实例的InetSocketAddress 对象,只在实例化对象时解析hostName与hostAddress的映射关系。
修复方法 可以借鉴 zookeeper 的 StaticHostProvider.java
调用next()
方法在解析一次域名与IP地址的映射关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public InetSocketAddress next (long spinDelay) { boolean needToSleep = false ; InetSocketAddress addr; synchronized (this ) { if (this .reconfigMode) { addr = this .nextHostInReconfigMode(); if (addr != null ) { this .currentIndex = this .serverAddresses.indexOf(addr); return this .resolve(addr); } this .reconfigMode = false ; needToSleep = spinDelay > 0L ; } ++this .currentIndex; if (this .currentIndex == this .serverAddresses.size()) { this .currentIndex = 0 ; } addr = (InetSocketAddress)this .serverAddresses.get(this .currentIndex); needToSleep = needToSleep || this .currentIndex == this .lastIndex && spinDelay > 0L ; if (this .lastIndex == -1 ) { this .lastIndex = 0 ; } } if (needToSleep) { try { Thread.sleep(spinDelay); } catch (InterruptedException var7) { LOG.warn("Unexpected exception" , var7); } } return this .resolve(addr); } private InetSocketAddress resolve (InetSocketAddress address) { try { String curHostString = address.getHostString(); List<InetAddress> resolvedAddresses = new ArrayList (Arrays.asList(this .resolver.getAllByName(curHostString))); if (resolvedAddresses.isEmpty()) { return address; } else { Collections.shuffle(resolvedAddresses); return new InetSocketAddress ((InetAddress)resolvedAddresses.get(0 ), address.getPort()); } } catch (UnknownHostException var4) { LOG.error("Unable to resolve address: {}" , address.toString(), var4); return address; } }
扩展 JVM的DNS缓存时间可用通过一下参数配置:
1 2 3 4 5 #globally, for all applications that use the JVM. Set networkaddress.cache.ttl in the $JAVA_HOME/jre/lib/security/java.security file networkaddress.cache.ttl #for your application only, set networkaddress.cache.ttl in your application’s initialization code: sun.net.inetaddr.ttl
networkaddress.cache.ttl
是全局参数,所有使用该JVM的程序
sun.net.inetaddr.ttl
JVM应用的配置参数,一般通过 -Dsun.net.inetaddr.ttl=10
j进行配置
参考