背景
近期業(yè)務(wù)出現(xiàn)一次問題爽哎,三方服務(wù)受到攻擊,然后其進(jìn)行緊急處理吏垮,將域名指向緊急修改為一個(gè)備用機(jī)房,但是發(fā)現(xiàn)流量沒有按照預(yù)期切換過去罐旗,懷疑是DNS的問題膳汪,所以稍微話時(shí)間看了下。
分析
引子
java里面有一個(gè)InetAddress.getByName()
方法九秀,可以將域名轉(zhuǎn)換為ip遗嗽,我們嘗試一下
public static void main(String[] args) throws UnknownHostException {
System.out.println(InetAddress.getByName("www.baidu.com"));
}
打印出來的結(jié)果是www.baidu.com/112.80.248.75
關(guān)鍵代碼
InetAddress[] addresses = getCachedAddresses(host);
/* If no entry in cache, then do the host lookup */
if (addresses == null) {
addresses = getAddressesFromNameService(host, reqAddr);
}
從上面可見,基本ip查詢分兩步
- 從緩存里面取結(jié)果
- 如果緩存里面不存在鼓蜒,則從域名服務(wù)器獲取結(jié)果
getCachedAddresses
/*
* Lookup hostname in cache (positive & negative cache). If
* found return addresses, null if not found.
*/
private static InetAddress[] getCachedAddresses(String hostname) {
hostname = hostname.toLowerCase();
// search both positive & negative caches
synchronized (addressCache) {
cacheInitIfNeeded();
CacheEntry entry = addressCache.get(hostname);
if (entry == null) {
entry = negativeCache.get(hostname);
}
if (entry != null) {
return entry.addresses;
}
}
// not found
return null;
}
里面邏輯很簡單痹换,從兩個(gè)cache(成功查詢的緩存和查詢失敗的緩存)里面嘗試獲取已緩存的域名映射ip征字,再細(xì)看Cache
的get
方法
/**
* Query the cache for the specific host. If found then
* return its CacheEntry, or null if not found.
*/
public CacheEntry get(String host) {
int policy = getPolicy();
if (policy == InetAddressCachePolicy.NEVER) {
return null;
}
CacheEntry entry = cache.get(host);
// check if entry has expired
if (entry != null && policy != InetAddressCachePolicy.FOREVER) {
if (entry.expiration >= 0 &&
entry.expiration < System.currentTimeMillis()) {
cache.remove(host);
entry = null;
}
}
return entry;
}
這里面涉及一個(gè)緩存的策略InetAddressCachePolicy
,即涉及緩存的有效時(shí)間娇豫,有如下幾個(gè)值的說明
public static final int FOREVER = -1;
public static final int NEVER = 0;
/* default value for positive lookups */
public static final int DEFAULT_POSITIVE = 30;
/* The Java-level namelookup cache policy for successful lookups:
*
* -1: caching forever
* any positive value: the number of seconds to cache an address for
*
* default value is forever (FOREVER), as we let the platform do the
* caching. For security reasons, this caching is made forever when
* a security manager is set.
*/
private static int cachePolicy = FOREVER;
/* The Java-level namelookup cache policy for negative lookups:
*
* -1: caching forever
* any positive value: the number of seconds to cache an address for
*
* default value is 0. It can be set to some other value for
* performance reasons.
*/
private static int negativeCachePolicy = NEVER;
-
FOREVER
表示永久緩存匙姜,小于0的值都認(rèn)為是永久緩存 -
NEVER
表示不緩存 -
DEFAULT_POSITIVE
表示默認(rèn)緩存時(shí)間,這邊是30秒 - 任意正整數(shù)(單位秒)
那java是如何設(shè)置緩存的Policy的呢冯痢,我們?cè)?code>InetAddressCachePolicy這個(gè)類里面可以看出一些端倪
// Controls the cache policy for successful lookups only
private static final String cachePolicyProp = "networkaddress.cache.ttl";
private static final String cachePolicyPropFallback =
"sun.net.inetaddr.ttl";
// Controls the cache policy for negative lookups only
private static final String negativeCachePolicyProp =
"networkaddress.cache.negative.ttl";
private static final String negativeCachePolicyPropFallback =
"sun.net.inetaddr.negative.ttl";
這四個(gè)屬性氮昧,是用來控制ttl時(shí)間,分別如下
-
networkaddress.cache.ttl
查詢成功的緩存時(shí)間(第一優(yōu)先級(jí)讀认敌摺) -
sun.net.inetaddr.ttl
查詢成功的緩存時(shí)間(第二優(yōu)先級(jí)讀取) -
networkaddress.cache.negative.ttl
查詢失敗的緩存時(shí)間(第一優(yōu)先級(jí)讀劝郧佟) -
sun.net.inetaddr.negative.ttl
查詢失敗的緩存時(shí)間(第二優(yōu)先級(jí)讀冉氛瘛)
個(gè)人推斷是為了做一些兼容,導(dǎo)致這個(gè)邏輯的產(chǎn)生梧乘,而且其獲取的方式也不一樣澎迎,第一優(yōu)先級(jí)的屬性用的是Security.getProperty(cachePolicyProp);
方式獲取,第二優(yōu)先級(jí)的是用的System.getProperty(cachePolicyPropFallback);
方式獲取选调。我們從jdk附帶的配置文件java.security
可以看出
#
# The Java-level namelookup cache policy for successful lookups:
#
# any negative value: caching forever
# any positive value: the number of seconds to cache an address for
# zero: do not cache
#
# default value is forever (FOREVER). For security reasons, this
# caching is made forever when a security manager is set. When a security
# manager is not set, the default behavior in this implementation
# is to cache for 30 seconds.
#
# NOTE: setting this to anything other than the default value can have
# serious security implications. Do not set it unless
# you are sure you are not exposed to DNS spoofing attack.
#
#networkaddress.cache.ttl=-1
# The Java-level namelookup cache policy for failed lookups:
#
# any negative value: cache forever
# any positive value: the number of seconds to cache negative lookup results
# zero: do not cache
#
# In some Microsoft Windows networking environments that employ
# the WINS name service in addition to DNS, name service lookups
# that fail may take a noticeably long time to return (approx. 5 seconds).
# For this reason the default caching policy is to maintain these
# results for 10 seconds.
#
#
networkaddress.cache.negative.ttl=10
所以有如下結(jié)論
- 成功查詢的緩存security文件沒有配置夹供,java底層代碼默認(rèn)設(shè)置30s
- 失敗查詢security文件設(shè)置的是10s
如果我們需要自行設(shè)置該值,可以調(diào)用
Security.setProperty("networkaddress.cache.ttl","100");
Security.setProperty("networkaddress.cache.negative.ttl","100");
或者
System.setProperty("sun.net.inetaddr.ttl","100");
System.setProperty("sun.net.inetaddr.negative.ttl","100");
getAddressesFromNameService
關(guān)鍵代碼行
addresses = nameService.lookupAllHostAddr(host);
而nameService的初始化如下
// create the impl
impl = InetAddressImplFactory.create();
// get name service if provided and requested
String provider = null;;
String propPrefix = "sun.net.spi.nameservice.provider.";
int n = 1;
nameServices = new ArrayList<NameService>();
provider = AccessController.doPrivileged(
new GetPropertyAction(propPrefix + n));
while (provider != null) {
NameService ns = createNSProvider(provider);
if (ns != null)
nameServices.add(ns);
n++;
provider = AccessController.doPrivileged(
new GetPropertyAction(propPrefix + n));
}
// if not designate any name services provider,
// create a default one
if (nameServices.size() == 0) {
NameService ns = createNSProvider("default");
nameServices.add(ns);
}
一般情況下仁堪,我們是不會(huì)自動(dòng)以nameserver的哮洽,所以,其會(huì)落到這一步代碼
NameService ns = createNSProvider("default");
nameService = new NameService() {
public InetAddress[] lookupAllHostAddr(String host)
throws UnknownHostException {
return impl.lookupAllHostAddr(host);
}
public String getHostByAddr(byte[] addr)
throws UnknownHostException {
return impl.getHostByAddr(addr);
}
};
impl
是一個(gè)接口InetAddressImpl
弦聂,也是挺詭異的鸟辅,其有兩個(gè)實(shí)現(xiàn)Inet4AddressImpl
和Inet6AddressImpl
,它們最終都是調(diào)用一個(gè)native方法
public native InetAddress[]
lookupAllHostAddr(String hostname) throws UnknownHostException
這是一個(gè)本地方法的調(diào)用莺葫,其會(huì)查詢/etc/hosts和使用/etc/resolv.conf里面配置的nameserver來進(jìn)行查詢匪凉。而本地方法也會(huì)涉及一些DNS緩存的事情,這邊就暫時(shí)不詳細(xì)說明了捺檬。
結(jié)論
一般對(duì)Java應(yīng)用程序而言再层,其DNS緩存分幾層
- jdk里面的緩存,它會(huì)緩存成功查詢和失敗查詢堡纬,成功查詢默認(rèn)緩存30s聂受,失敗查詢默認(rèn)緩存10s,所以一旦DNS查詢失敗烤镐,會(huì)有10s業(yè)務(wù)中斷
- 平臺(tái)有緩存饺饭,這里面的平臺(tái)指的是linux或者windows平臺(tái),它們的nscd等服務(wù)點(diǎn)擊參考會(huì)對(duì)查詢域名進(jìn)行一段時(shí)間的緩存职车,當(dāng)然如果機(jī)器上面沒有此類服務(wù)瘫俊,則默認(rèn)沒有DNS緩存
- 域名服務(wù)器的緩存鹊杖,由于存在不同的域名服務(wù)器,它們內(nèi)部的緩存時(shí)間就不定了扛芽。