安卓系统dns缓存策略

背景
安卓客户端上报出不少UnknownHostException,引起了大家的重视,于是决定深入研究一番。
UnknownHostException是个什么异常?让我们来谷歌的源代码是怎么说的:

/**
* Thrown when a hostname can not be resolved.
*/

原来就是说域名无法被解析的时候会抛这个异常啊。那我就先来大致了解一下域名解析在客户端是怎么做的吧。
 

基本原理
通常客户端在进行接口调用、页面访问的时候都会通过域名来定位目标主机,从而与目标主机中的服务进行通信。不过,要定位一个进程,需要的是IP+端口号。所以我们要先把域名解析成IP形式。
这个过程大致是:客户端向域名服务器发送一个UDP请求,查询域名相对应的IP。服务器收到查询请求后,向客户端返回相应的IP。好吧,没有这么简单,实际过程要复杂得多。


我们这次会把注意力集中在客户端一侧,至于如何递归查询就不深入讨论了。下面我们抓包举例,这是友盟sdk在向它的服务器上传数据的开始过程。

 


首先,发送DNS查询请求。很幸运,响应很快就返回了,而且IP地址还不止一个。是的,DNS查询的返回可能会是多个IP地址。
然后,客户端向其中一个IP地址发起了经典的3次握手:SYN – SYN,ACK – ACK。
接下来,就可以发送数据了。
我们发现,DNS查询需要向域名服务器发请求,DNS服务器还有可能会进行一系列的递归查询。这是一个并不高效的过程,而且实际上域名所对应的IP并不经常变化。为了提高效率,工程师们就开始对DNS查询的结果进行层层缓存。

 

 

 

DNS缓存

言归正传,我们看看安卓系统到底都做了什么缓存。
安卓系统对DNS缓存 有两个地方,一个是虚拟机层,一个是框架层 java.net.InetAddress类内部维护了一个缓存。系统会先从框架层缓存中查找,如果没有找到,再到虚拟机层查找。缓存命中,则直接返回缓存IP,而不会真的发送DNS请求。
缓存的有效期被称为TTL(time to live)。
DNS查询结果缓存分2种:成功(positive)的查询和失败(negative)的查询(比如域名不存在)。而系统不仅会缓存成功的查询结果,还会把失败的结果也缓存起来!
缓存时效的修改
1)虚拟机层

 

Security.setProperty("networkaddress.cache.ttl", String.valueOf(0));  
Security.setProperty("networkaddress.cache.negative.ttl", String.valueOf(0))

参数单位为秒,0代表不缓存,-1代表永久缓存(慎用)。
2)框架层
很不幸,框架层的缓存有效期是无法修改的。让我们来看看AddressCache.java:

// Default time-to-live for positive cache entries. 600 seconds (10 minutes).
private static final long DEFAULT_POSITIVE_TTL_NANOS = 600 * 1000000000L;
// Default time-to-live for negative cache entries. 10 seconds.
private static final long DEFAULT_NEGATIVE_TTL_NANOS = 10 * 1000000000L;

成功的查询缓存10分钟,失败的查询缓存10秒钟。
上面是OS v4.1及以下版本的实现。就是说当一次查询失败后,框架层会把错误信息直接作为value缓存起来,并保持10秒的有效期。之后10秒内对于同一个域名的查询都会直接抛出UnknownHostException。

/**
  * Records that 'hostname' is known not to have any associated addresses. (I.e. insert a
  * negative cache entry.)
  */
public void putUnknownHost(String hostname, String detailMessage) {
    put(hostname, detailMessage);
}

这个设计有些不够人性,对于接口访问密集的场景,会造成大量UnknownHostException。估计谷歌也意识到了这个问题,在v4.2之后,框架层成功和失败的缓存时效都变成了2秒。

// The TTL for the Java-level cache is short, just 2s.
private static final long TTL_NANOS = 2 * 1000000000L;

 

业务场景分析
结合App的场景,10秒的缓存时效的确有一定问题。如果一个接口发生域名解析失败,接下来的10秒内所有接口都会发生UnknownHostException。即便没有集中的接口访问,若安卓客户端10秒内重试,那么必然会再发生一次UnknownHostException。这便造成了安卓客户端在一定程度上UnknownHostException数量的升高。
不过,目前大部分设备的系统版本都比较高,仍然需要进一步研究,找出问题根源。

 

 

-------------------------------------原创内容,转载请说明出处-----------------------------------------

欢迎联系

微信:oscaryue001

 

### 如何在 Android 设备上清除 DNS 缓存Android 系统中,DNS 缓存可以通过多种方式被管理和清除。以下是几种常见的方法: #### 方法一:通过命令行工具清除 DNS 缓存 对于开发者模式启用的设备,可以利用 `adb` 命令来操作系统的网络设置并间接清除 DNS 缓存。 运行以下 ADB 命令可重置网络配置: ```bash adb shell settings put global captive_portal_server google.com ``` 此命令会触发系统重新加载网络状态,从而清空部分缓存数据[^1]。 另外,在某些版本中可以直接尝试重启 Network Service 来达到目的: ```bash adb shell stop netd && adb shell start netd ``` #### 方法二:修改应用程序层面的行为 如果目标仅限于 Java 层面的应用程序内部,则可通过强制刷新 InetAddress 的实现机制完成清理工作。例如下面这段代码展示了如何手动释放已有的地址解析记录: ```java import java.net.InetAddress; import java.util.Collections; public class DnsClearExample { public static void main(String[] args) throws Exception { // 获取本地主机名对应的 IP 地址对象列表 Collections.list(InetAddress.getAllByName("example.com")); System.setProperty("networkaddress.cache.ttl", "0"); // 设置 TTL 为零秒 // 再次查询时不会命中之前的缓存条目 Collections.list(InetAddress.getAllByName("example.com")); } } ``` 注意这里调整的是 JVM 参数 networkaddress.cache.ttl 和 networkaddress.cache.negative.ttl ,分别控制正向与反向查找的有效期长度(单位均为秒)。将其设成负数或者很小正值即可让后续请求忽略已有存储项[^2]。 #### 方法三:针对特定 API 版本的操作 自 Android P (API level 28),官方引入了一个新的类 android.net.DhcpResults 。虽然它主要用于 DHCP 租约管理而非单纯处理域名映射关系,但我们仍然可以从其返回结构里找到一些线索用于辅助调试或验证实际效果。更重要的是,随着架构演进到更高层次之后,底层 libcore/bionic 部分的功能逐渐被淘汰替换成了更现代化的设计方案——即前面提到过的 Netd Resovler 组件体系[^3]。 因此,当面对较新型号终端的时候,可能需要额外关注这些变化所带来的影响以及相应的适配策略。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值