百度 App 网络深度优化系列(番外篇):IPv6 下 Happy Eyeballs 的最佳实践

一、前言

IPv6 是当下如火如荼的话题,由于 IPv4 地址的耗尽,所以 IPv6 的切换已经势在必行。但在 IPv6 的初期,由于基础建设还不完善,IPv6 可能会出现连通性或可靠性的问题,那我们该如何从 IPv4 平稳过渡到 IPv6 呢?

目前业内标准的做法叫 Happy Eyeballs,什么叫 Happy Eyeballs 呢?就是不会因为 IPv4 或 IPv6 的故障问题,导致用户的眼球一直在等待加载或者出错,这就是 Happy Eyeballs 名字的由来。

二、背景

Happy Eyeballs 解决的核心问题是,复杂环境下 v4 和 v6 IP 选取的问题,它是一套整体解决方案,对于域名查询的处理,地址的排序,连接的尝试等方面均做出了规定。

Happy Eyeballs 有 v1 版本 RFC6555(Cisco 提出来的)和 v2 版本 RFC8305(Apple 提出来的)。具体的协议规范可参考资料【1】和【2】。我们从百度 App 对于 Happy Eyeballs 的实践出发,剖析下百度 App 是如何实现 Happy Eyeballs 的。

三、最佳实践

百度 App 的 Happy Eyeballs 最佳实践,如下图所示。

Happy Eyeballs 最佳实践

最佳实践包括自有业务和核心组件(图片组件,音视频组件,WebView 组件)底层网络库的流量接管。

网络库包括 FFmpeg 的网络模块(实现了 RFC8305 的 Happy Eyeballs),okhttp(实现了 RFC6555 的 Happy Eyeballs),统一网络库 cronet(实现了 RFC6555 的 Happy Eyeballs)。下面我们来看下这三个网络库的 Happy Eyeballs 的实现机制。

1.FFmpeg 的 Happy Eyeballs 实现机制

我们从域名查询的处理、地址的排序、连接的尝试三个方面来说明。

FFmpeg Happy Eyeballs 实现机制

域名查询的处理,FFmpeg 主要流量走的是 localDNS,会将 v4 和 v6 地址一并查询回来,DNS query 中 A 记录表示 v4 查询,Type 是 1,AAAA 记录表示 v6 查询,Type 是 28。具体协议可以参考下面两图。

IPv4 的 Query

IPv6 的 Query

地址的排序,如果查询回来是多个 v4 和 v6 的地址,会将查询结果保存在 addrinfo 这个结构体里,它是一个链表结构,会将这个链表交替排序,v6 在前,v4 在后,比如查询回来的是 [第一个 v4 地址,第二个 v4 地址,第一个 v6 地址,第二个 v6 地址],排序之后变成 [第一个 v6 地址,第一个 v4 地址,第二个 v6 地址,第二个 v4 地址]。

连接的尝试,立即连接 v6 地址,FFmpeg 可以让使用者设置连接超时时间,会将这个超时时间和 200ms(这是 RFC8305 建议的值)进行取小操作,如果在较小值以内 v6 建连成功,则结束,若以外将发起下一个 v4 连接,与此同时关闭当前的 v6 连接。延迟发送的 v4 连接将重复上面的操作,如果成功则结束,如果超时将发起下一个 v6 连接,与此同时关闭当前的 v4 连接。直到没有 ip 地址。

2.cronet 的 Happy Eyeballs 实现机制

我们从域名查询的处理、地址的排序、连接的尝试三个方面来说明。

cronet Happy Eyeballs 实现机制

域名查询的处理,百度 App 的 cronet 主要流量走的是 HTTPDNS,HTTPDNS 请求会将一个域名的 v4 地址和 v6 地址同时返回。

地址的排序,cronet 开启 Happy Eyeballs 有两个条件,一是查询结果第一个地址是 v6 的,二是查询结果里包含 v4 和 v6 的地址。cronet 会优先将 v6 地址放入结果列表,保证了第一点。

连接的尝试,立即连接 v6 地址并开启一个延迟 300ms(这是 RFC6555 建议的值)发起 v4 的建连任务,如果在 300ms 以内 v6 建连成功,延迟任务直接终止,流程结束。若以外会将结果列表进行 rotate,按顺序将列表里的第一个 v4 地址旋转到第一个,发起 v4 连接,此时将开启一个竞争模式,当 v4 开始建立连接和建连成功后,都会去检查 v6 是否成功,只要 v6 在竞争模式内建连成功,则使用 v6 的连接,v6 没有建连成功,则使用 v4 连接。

3.okhttp 的 Happy Eyeballs 实现机制

okhttp Happy Eyeballs 实现机制

百度 App 依照 cronet 的实现细节,遵循 RFC6555 的规范,实现了 okhttp 版本的 Happy Eyeballs,由于实现细节和 cronet 类似,所以就不在这里进行讲解。主体流程如上图,通过 okhttp 的请求并发控制模块(核心定义了最大线程数 64 个和单域名的最大并发数 5 个),再来到核心拦截器模块,包括对于 Header 和 Cookie 处理的拦截器,对于 Cache 处理的拦截器,对于连接处理的拦截器,在连接处理的拦截器里首先是从连接池获取连接,获取不到就会新创建连接,Happy Eyeballs 的实现就会加到这里。

4.WKWebView 的 Happy Eyeballs 实现

在某些场景下 WKWebView 没有被 cronet 网络库进行接管,所以还需要依赖苹果自身的 Happy Eyeballs 机制,在这里就不多赘,感兴趣的同学可以参考资料【3】,苹果的工程师会详细讲解如何实现 Happy Eyeballs 的机制以及 Happy Eyeballs 下走 v6 的测试结果。

四、结语

IPv6 的进程是任重而道远的,Happy Eyeballs 做为从 v4 过渡到 v6 的重要规范必然会起到它应有的使命,详尽了解它并使用它应该是每个相关工程师的职责,希望本次番外篇对大家有帮助,感谢大家的辛苦阅读。

参考资料

[1] https://tools.ietf.org/html/rfc6555

[2] https://tools.ietf.org/html/rfc8305

[3] https://mailarchive.ietf.org/arch/msg/v6ops/DYiI9v_O66RNbMJsx0NsatFkubQ

[4] https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/9b4c3f5aadf54ffd2a6e15746b1fd736379883c4

作者介绍:

蔡锐,9 年移动端开发经验,在百度先后主导过订制 ROM 领域、多屏互动领域、Hybrid 跨平台领域等多个技术领域的开发,目前担任百度 App 的客户端资深工程师,参与基础技术的研究,专攻动态化、性能和网络优化方向。获得 2019 软件绿色联盟最佳人气讲师、2019ArchSummit 大前端专题明星讲师。