jdk的InetAddress有一个特性,就是当系统访问过一个域名的时候,InetAddress就会通过其私有变量addressCache把域名对应的ip地址缓存起来.
虽然缓存起来能极大的提高系统性能,但有时候会给系统带来很大的麻烦.例如,当对方改动了ip地址后,系统就不能再访问到新的ip地址了,这个时候最直接的方案就是:重启jvm!!!这对于需要7*24小时服务的系统来说,是不可忍受的.
下面一段代码可以重现这个现象(但需要你在运行的时候是在调试模式):
public void testDnsCachePolicy() throws Exception {
InetAddress addr1 = InetAddress.getByName("www.baidu.com");
System.out.println(addr1.getHostAddress());
//在下一行设置断点.
int i = 0;
InetAddress addr2 = InetAddress.getByName("www.baidu.com");
System.out.println(addr2.getHostAddress());
}
具体测试方式是:
1.修改c:/windows/system32/drivers/etc/hosts文件,在文件末尾加入:64.233.189.104 www.baidu.com这个ip地址是google的ip
2.运行代码到断点处
这时候打印出的ip地址是64.233.189.104
3.修改hosts文件,把"64.233.189.104 www.baidu.com"这行注释掉,"#64.233.189.104 www.baidu.com"
4.继续运行代码到结束
这时候打印出的ip地址还是64.233.189.104,并没有更改为baidu的ip地址.
那么应该怎么来解决这个问题呢?
查了下网上的解决方案,一般是在启动jvm的时候,指定jvm参数:networkaddress.cache.ttl和networkaddress.cache.negative.ttl,具体的含义你可以查看InetAddress的源代码.
这种方法的缺点是在JVM启动的时候就固定了dns的缓存策略.如果不缓存的话,对系统性能的影响是很大的,那么能不能动态的修改这个缓存的值呢?
正好前段时间写了篇文章:怎么通过反射修改类的私有字段值.正好有了用武之地!
下面是测试代码:
//方法中的字符串常量policy,cache,addressCache请参考InetAddress源代码.
public void testDnsCachePolicy() throws Exception {
InetAddress addr1 = InetAddress.getByName("www.baidu.com");
System.out.println(addr1.getHostAddress());
//在下一行设置断点.
int i = 0;
//修改缓存数据开始
Class inetAddressClass = java.net.InetAddress.class;
final Field cacheField = inetAddressClass.getDeclaredField("addressCache");
cacheField.setAccessible(true);
final Object obj = cacheField.get(inetAddressClass);
Class cacheClazz = obj.getClass();
final Field cachePolicyField = cacheClazz.getDeclaredField("policy");
final Field cacheMapField = cacheClazz.getDeclaredField("cache");
cachePolicyField.setAccessible(true);
cacheMapField.setAccessible(true);
final Map cacheMap = (Map)cacheMapField.get(obj);
cacheMap.remove("www.baidu.com");
//修改缓存数据结束
InetAddress addr2 = InetAddress.getByName("www.baidu.com");
System.out.println(addr2.getHostAddress());
}
重新按照上面的测试方法测试一次,第2次已经能够拿到正确的ip地址了.
如果在用apache的httpclient,那么,在把缓存中的数据清除后,需要重新创建GetMethod/PostMethod对象.
例如:
HttpClient client = new HttpClient();
GetMethod m1 = new GetMethod("http://www.baidu.com");
client.executeMethod(m1);
String content = m1.getResponseBodyAsString();
........//通过上面的反射方法清楚缓存
//重新执行m1,仍然不会得到正确的结果
client.executeMethod(m1);
String content = m1.getResponseBodyAsString();
//重新创建GetMethod,才能得到正确的结果
GetMethod m2 = new GetMethod("http://www.baidu.com");
client.executeMethod(m2);
content = m2.getResponseBodyAsString();