从7月中旬左右,我们客户端更新失败率由原来的2%上升到10%。更新后台数据统计显示更新失败中的90%为HTTP下载失败,具体的失败原因是文件下载完成后MD5与服务器预期的MD5不匹配。

在着手调查解决这个问题时,第一个怀疑的点是客户端下载器我希望能在代码里找到发生以下两种情况的可能性:一种是客户端在代码是否会导致文件下载不完整,另一种则是客户端的HTTP访问有没有明确禁用Http Cache。但客户端代码的Code Review表明每次Http请求都在Http头中明确启动了禁用Cache标志,对HTTP文件下载文件长度校验也吻合Http1.1的规范。

在第一次分析失败的基础上,我系统地整理了客户端从构建完成直至其被传送到最终用户机器上的全部流程,从完整的数据流路径中来分析问题的可能性。这个大的路径分天然地分成了两个独立的部分。

一是目标文件的部署过程


为了避免可能存在的cache,我们客户端的每个新版本发布,都会在cdn源站上以此版本号创建创建一个全新的目录,下载地址形式如下:

HTTP://域名/版本号/子目录/子目录/文件名

部署过程的URI配置、源和目录配置都有完整的手工验证,这儿不会出现错误。唯一有可能出错的过程是Rysnc目标文件到CDN源站的过程,但事实上如果Rsync出错,则影响的升级错误量不可能只有10%这么少。所以部署流程中的错误可以全部排除。

二是客户端的下载过程


更进一步地,把把客户端的下载过程进行细分,可以得到两个子流程,即DNS解析过程和客户端文件传输过程:


       

我们尝试从客户端下载的这两个子流程中去猜测错误发生的可能性:

1. ISP DNS或者CNAME服务器出错,返回了不正确的CDN节点IP给客户端,导致客户端连接上了错误的目标站点,从而下载不到任何文件或者被拒绝访问。

2. CDN节点有可能存在不正确的文件缓存机制,导致客户端访问到的是此文件的历史版本。

3. 这些失败的用户中的大部分为网吧用户,因为网吧的网管软件升级和客户端升级相冲突,可能存在不正确的历史版本文件缓存。

4. ISP网关或ISP中间服务器存在不正确的文件缓存机制,导致客户端访问到的文件是此文件的历史版本。

针对可能性1,我们从错误日志中分析得到失败最多的10个CDN节点IP (来自这些IP的失败占了总失败的80%以上),经和CDN厂商确认,均是其CDN边缘节点,故障1可以排除。

针对可能性2,我们从10个失败最多的CDN节点里抽样调查并下载了数个校验失败文件, 结果显示CDN节点上的数据准确无误。故障2可以排除。

剩下的问题就只有可能是猜测3或猜测4,而这两个猜测均指向了文件缓存机制(http cache).于是我们继续进行错误日志分析,希望能找到历史版本中的文件MD5和失败时的MD5匹配。而日志分析结果也确实证明了这一点,日志的分析结果如下:

1.文件下载校验失败只能解释为Cache失败--这些校验失败的文件的MD5,95%以上对应的是我们某一些客户端历史版本该文件的MD5。也就是说客户端总是下载到历史上存在该文件。

2.下载失败的客户端IP散落在广大的北方,其中80%以上是联通节点的失败。但是IP分布并无规律,不集中在一些地区。

3.校验失败的文件总是那四个4个文件,它们的检验失败占了总校验失败的90%以上

4.失败的IP不在我们能查询到的网吧IP列表当中

5.失败的客户端安装目录无网吧安装目录结构特征。

6.失败的客户端网吧管理软件检测显阳性的样本占总失败的比例不到1%

日志分析结果4、5、6项网吧的可能性被排除在外,那么这段时间的文件校验失败,最大可能客户端网络接入商这一环有问题--某些二三级网络服务接入商为了节省给最终用户的带宽,而仅仅使用域名+文件名作为Cache KEY来实现HTTP下载Cache.且不管用户在HTTP访问时是否带有No Cache标志,它都以自己的Cache命中的文件优先返回给了最终用户。这样的解释在理论上说得通,也能与我们遇到的失败率(10%左右)相一致。

针对可能存在的这种不良Cache策略,我们尝试解决方案如下:

对每一个新版本的客户端升级文件压缩包的名字前带上编译号前缀,这样看起来每次升级,它的文件名和目录均不同。新的客户端升级URL形式为:

HTTP://域名/版本号/子目录/子目录/编译号_文件名

假如ISP的HTTP Cache策略如我们所推测,那么他们仅使用的域名文件名作为Cache Key在我们修改文件名之后就会失效,相应的就会CDN节点上获取正确的文件版本,最终客户端的下载就会成功。

在此方案实施后,客户端升级校验失败率降到了0.5%,问题得以解决.