最近在处理某个Java 应用的 UnknownHostException 异常的时候, 发现公司的应用框架团队提供了一个 MXBean, 这个 MXBean 可以提供该应用的 DNS 查询记录, 包括成功的记录, 失败的记录, 记录的失效时间等信息.
这些信息有什么用呢?
- 根据这些 DNS 查询信息, 大概了解我们的 Java 应用访问了哪些应用, 有没有不是期望的查询;
- 哪些 FQDN 查询是失败的? 为什么失败? 是不是不应该访问这些 FQDN;
- 根据这些失效时间, 以及多次刷新, 判断是不是一直调用其他服务;
如何获得这些DNS查询信息
DNS 的查询信息都在 InetAddress 这个 Java 类的 2 个私有的静态字段里面. addressCache 和 negativeCache 分别表示成功的 DNS 查询和失败的 DNS 查询.
private static Cache addressCache = new Cache(Cache.Type.Positive);
private static Cache negativeCache = new Cache(Cache.Type.Negative);
Java 里面的 DNS TTL 设置
JVM 层面默认缓存 DNS 查询结果的, 有些 JVM 会一直缓存这些结果. 所以可以通过设置 2 个参数来配置缓存多久. 这个 2 个参数分别是:
networkaddress.cache.ttl=60
networkaddress.cache.negative.ttl (default: 10)
可以通过修改这个配置文件 $JAVA_HOME/jre/lib/security/java.security 来修改, 或者通过启动 JVM 的时候 给参数来设置.
更多相关信息, 可以参考:
https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-jvm-ttl.html
https://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html
具体代码
Google search "Java read DNS cache", 第一个结果就给出了很好的代码, 就是这个问答:
https://stackoverflow.com/questions/1835421/java-dns-cache-viewer
它通过不断的反射, 去拿到里面那些私有的数据.
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class DNSCache {
public static void main(String[] args) throws Exception {
// put some values in the internal DNS cache
// good DNS name
InetAddress.getByName("stackoverflow.com");
InetAddress.getByName("www.google.com");
InetAddress.getByName("www.rgagnon.com");
try {
// bad DNS name
InetAddress.getByName("bad.rgagnon.com");
}
catch (UnknownHostException e) {
// do nothing
}
// dump the good DNS entries
String addressCache = "addressCache";
System.out.println("---------" + addressCache + "---------");
printDNSCache(addressCache);
// dump the bad DNS entries
String negativeCache = "negativeCache";
System.out.println("---------" + negativeCache + "---------");
printDNSCache(negativeCache);
}
/**
* By introspection, dump the InetAddress internal DNS cache
*
* @param cacheName can be addressCache or negativeCache
* @throws Exception
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void printDNSCache(String cacheName) throws Exception {
Class<InetAddress> iaclass = InetAddress.class;
Field acf = iaclass.getDeclaredField(cacheName);
acf.setAccessible(true);
Object addressCache = acf.get(null);
Class cacheClass = addressCache.getClass();
Field cf = cacheClass.getDeclaredField("cache");
cf.setAccessible(true);
Map<String, Object> cache = (Map<String, Object>) cf.get(addressCache);
for (Map.Entry<String, Object> hi : cache.entrySet()) {
Object cacheEntry = hi.getValue();
Class cacheEntryClass = cacheEntry.getClass();
Field expf = cacheEntryClass.getDeclaredField("expiration");
expf.setAccessible(true);
long expires = (Long) expf.get(cacheEntry);
Field af = cacheEntryClass.getDeclaredField("addresses"); // JDK 1.7, older version maybe "address"
af.setAccessible(true);
InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry);
List<String> ads = new ArrayList<String>(addresses.length);
for (InetAddress address : addresses) {
ads.add(address.getHostAddress());
}
System.out.println(hi.getKey() + " "+new Date(expires) +" " +ads);
}
}
}