URLCache 设置不当引起的 App 故障

关于数据请求是几乎每个 App 会经常遇到的事,关于数据的 Request 与 Response 及 Cache 其实我们需要了解更多细节才能运用自如,排查问题更快。

很久之前,我时常遇到一部分用户能打开页面,一部用户却不能的情况。在排除网络,地域,机型,服务器状态完全正常的情况,一直无法解释其原因。客户也说不上来有什么区别,操作路径也是一样,只是过一段时间就自然恢复了,或重装等等解决。

直到最近的一次故障让我彻底将此类问题的原头分析了一遍,才有所顿悟。

故障

现象 服务端因某种原因使所有 API 请求返回错误的,类似源代码的片段内容。几个小时后服务器恢复。不过部分用户使用时看到的仍然是出错页面,而其他用户又是正常。

影响范围 后来仔细分析发现,在服务器宕机的那段时间内,用户如果打开过 App ,则一直会看到出错页面。即使完全退出 App 再进入错误仍旧发生。如果这段时间内没有打开过 App 的用户,在之后打开的话,就会看到正常的页面。所以影响面是所有服务器出错这段时间内,打开过 App 的用户。

  1. 如果缓存内容不存在,不用说,直接发起原始请求了

  2. 如果缓存内容存在,确定是否每次从原始资源地址检查。这依赖于 Response Header 里 cach-control 指定的内容

    常见的值有 private、public、no-store、no-cache、must-revalidate、max-age等。

    no-cache: 告诉浏览器、缓存服务器,不管本地副本是否过期,使用资源副本前,一定要到源服务器进行副本有效性校验。

    must-revalidate:告诉浏览器、缓存服务器,本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验。

本例中 Response Header 没有指定 must-revalidate,所以条件是否。

  1. 如果结果为是,则发起 HEAD request 请求,查看内容是否有变更,"是"则请求网络数据,如果否就用缓存

  2. 如果上面的结果是否,则查看 Response 是否失效 (stale?)

这里的失效算法算是黑盒了,不过还好发现篇文章,描述了如何计算出这个失效期限。

// 首先是计算 freshness_lifetime

freshness_lifetime = (date_value - last_modified) * 10% =>

// 例如: ([Sat, 09 Apr 2016 10:26:51 GMT] - [Sun, 17 Jan 2016 14:15:33 GMT]) 10% => [82 days, 19 hours, 11 minutes and 18 seconds] 10% => 7,153,878 * 10% = 715388 seconds = 8 days 6 hours 43 minutes 8 seconds

// 然后判断 response 是否失效是根据 freshness lifetime 是否大于 current age response_isfresh = (freshness_lifetime > current_age)

// 这里的 current age 是根据当前时间 now 减去 Response Header 里的 date_value 来的 current_age = now - date_value</pre> 5. 如果计算的结果是失效的 stale 是 true ,则去发送 issue request 请求。如果是否返回缓存数据</li> </ol> </li> 解决

避免缓存无效的 Response首先服务端对于错误的返回结果不能返回 200 status code 客户端要做强校验,对于返回的不规范的格式或非业务所需,直接 removeCachedResponse

设置强制清理缓存机制可以由客户手动清理缓存,或由服务端控制清理

区别对待不同的 API 请求 cache 策略客户端与服务端对不同的请求设定不同的缓存策略,比如配置下发那些是强制刷新不缓存的。不经常变的资源可以设置更长的缓存期限

参考资料

NSURLCache

iOS5 Built-In HTTP DiskCache

A Primer In Http–caching And Its–native Support–by–ios

NSURLCache Uses a Disk Cache as of iOS 5

工具

iOS SDK 原生抓包工具

Last updated