我们使用 GSLB 进行地理分布和负载平衡。每个服务都分配了一个固定的域名。通过一些 DNS 魔术,域名被解析为最接近负载最少的服务器的 IP。为了使负载均衡发挥作用,应用服务器需要遵守来自 DNS 响应的 TTL,并在缓存超时时重新解析域名。但是,我想不出在 Java 中执行此操作的方法。
该应用程序采用 Java 5,在 Linux (Centos 5) 上运行。
根据 Byron 的回答,您不能通过使用标志或调用来设置networkaddress.cache.ttl
或作为系统属性,因为这些不是系统属性 - 它们是安全属性。networkaddress.cache.negative.ttl
-D
System.setProperty
如果您想使用 System 属性来触发此行为(因此您可以使用-D
flag 或 call System.setProperty
),您将需要设置以下System属性:
-Dsun.net.inetaddr.ttl=0
此系统属性将启用所需的效果。
但请注意:如果您在启动 JVM 进程时不使用该-D
标志并选择从代码中调用它:
java.security.Security.setProperty("networkaddress.cache.ttl" , "0")
此代码必须在 JVM 中的任何其他代码尝试执行网络操作之前执行。
这很重要,因为例如,如果您调用Security.setProperty
.war 文件并将该 .war 部署到 Tomcat,这将不起作用:Tomcat 使用 Java 网络堆栈在执行 .war 的代码之前进行初始化。-D
由于这种“竞争条件”,在启动 JVM 进程时使用标志通常更方便。
如果您不使用-Dsun.net.inetaddr.ttl=0
或调用Security.setProperty
,则需要$JRE_HOME/lib/security/java.security
在该文件中编辑和设置这些安全属性,例如
networkaddress.cache.ttl = 0
networkaddress.cache.negative.ttl = 0
但请注意围绕这些属性的评论中的安全警告。仅当您有理由确信自己不易受到DNS 欺骗攻击时才执行此操作。
Java 有一些非常奇怪的 dns 缓存行为。最好的办法是关闭 dns 缓存或将其设置为 5 秒之类的低值。
networkaddress.cache.ttl(默认值:-1)
指示从名称服务中成功查找名称的缓存策略。该值指定为整数,以指示缓存成功查找的秒数。-1 值表示“永远缓存”。networkaddress.cache.negative.ttl(默认值:10)
指示从名称服务中不成功的名称查找的缓存策略。该值指定为整数,以指示缓存不成功查找失败的秒数。值 0 表示“从不缓存”。-1 值表示“永远缓存”。
这显然已在较新的版本(SE 6 和 7)中得到修复。在使用 tcpdump 观看端口 53 活动时运行以下代码片段时,我遇到了最大 30 秒的缓存时间。
/**
* http://stackoverflow.com/questions/1256556/any-way-to-make-java-honor-the-dns-caching-timeout-ttl
*
* Result: Java 6 distributed with Ubuntu 12.04 and Java 7 u15 downloaded from Oracle have
* an expiry time for dns lookups of approx. 30 seconds.
*/
import java.util.*;
import java.text.*;
import java.security.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class Test {
final static String hostname = "www.google.com";
public static void main(String[] args) {
// only required for Java SE 5 and lower:
//Security.setProperty("networkaddress.cache.ttl", "30");
System.out.println(Security.getProperty("networkaddress.cache.ttl"));
System.out.println(System.getProperty("networkaddress.cache.ttl"));
System.out.println(Security.getProperty("networkaddress.cache.negative.ttl"));
System.out.println(System.getProperty("networkaddress.cache.negative.ttl"));
while(true) {
int i = 0;
try {
makeRequest();
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(new Date());
inetAddress = InetAddress.getByName(hostname);
displayStuff(hostname, inetAddress);
} catch (UnknownHostException e) {
e.printStackTrace();
}
try {
Thread.sleep(5L*1000L);
} catch(Exception ex) {}
i++;
}
}
public static void displayStuff(String whichHost, InetAddress inetAddress) {
System.out.println("Which Host:" + whichHost);
System.out.println("Canonical Host Name:" + inetAddress.getCanonicalHostName());
System.out.println("Host Name:" + inetAddress.getHostName());
System.out.println("Host Address:" + inetAddress.getHostAddress());
}
public static void makeRequest() {
try {
URL url = new URL("http://"+hostname+"/");
URLConnection conn = url.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
InputStreamReader ird = new InputStreamReader(is);
BufferedReader rd = new BufferedReader(ird);
String res;
while((res = rd.readLine()) != null) {
System.out.println(res);
break;
}
rd.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
要扩展拜伦的答案,我相信您需要编辑目录java.security
中的文件%JRE_HOME%\lib\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 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
有关文件的java.security
文档here。
总结其他答案,<jre-path>/lib/security/java.security
您可以设置属性的值networkaddress.cache.ttl
来调整 DNS 查找的缓存方式。请注意,这不是系统属性,而是安全属性。我可以使用以下方法进行设置:
java.security.Security.setProperty("networkaddress.cache.ttl", "<value>");
这也可以由系统属性设置,-Dsun.net.inetaddr.ttl
尽管如果在其他地方设置它不会覆盖安全属性。
我还想补充一点,如果您像我一样在 WebSphere 中看到 Web 服务的这个问题,那么设置networkaddress.cache.ttl
是不够的。您需要将系统属性设置disableWSAddressCaching
为true
. 与 time-to-live 属性不同,这可以设置为 JVM 参数或通过System.setProperty
)。
IBM 在此处有一篇关于 WebSphere 如何处理 DNS 缓存的非常详细的文章。与上述相关的部分是:
要禁用 Web 服务的地址缓存,您需要将附加的 JVM 定制属性 disableWSAddressCaching 设置为 true。使用此属性禁用 Web 服务的地址缓存。如果您的系统通常使用大量客户端线程运行,并且您在 wsAddrCache 缓存上遇到锁争用,那么您可以将此定制属性设置为 true,以防止缓存 Web 服务数据。
根据官方的 oracle java properties,sun.net.inetaddr.ttl
是 Sun 特定于实现的属性,“可能在未来的版本中不受支持”。“首选方法是使用安全属性” networkaddress.cache.ttl
。
所以我决定看一下java源代码,因为我发现官方文档有点混乱。我发现的(对于 OpenJDK 11)大多与其他人写的一致。重要的是属性评估的顺序。
InetAddressCachePolicy.java(为了便于阅读,我省略了一些样板文件):
String tmpString = Security.getProperty("networkaddress.cache.ttl");
if (tmpString != null) {
tmp = Integer.valueOf(tmpString);
return;
}
...
String tmpString = System.getProperty("sun.net.inetaddr.ttl");
if (tmpString != null) {
tmp = Integer.valueOf(tmpString);
return;
}
...
if (tmp != null) {
cachePolicy = tmp < 0 ? FOREVER : tmp;
propertySet = true;
} else {
/* No properties defined for positive caching. If there is no
* security manager then use the default positive cache value.
*/
if (System.getSecurityManager() == null) {
cachePolicy = 30;
}
}
您可以清楚地看到首先评估安全属性,然后评估系统属性,如果它们中的任何一个被设置,则cachePolicy
值被设置为该数字,或者-1 (FOREVER)
它们是否持有低于 -1 的值。如果未设置任何内容,则默认为 30 秒。事实证明,OpenJDK 几乎总是如此,因为默认情况下java.security
不设置该值,只有一个负值。
#networkaddress.cache.ttl=-1 <- this line is commented out
networkaddress.cache.negative.ttl=10
顺便说一句,如果networkaddress.cache.negative.ttl
未设置(从文件中删除),则 java 类中的默认值为 0。这方面的文档是错误的。这就是让我绊倒的原因。