如何做到以最小的成本、最小的风险和最大的收益接入 QUIC,是本文讨论的核心。
通过敏捷快速实践 QUIC ,网易新闻在3个月内,将端内 QUIC 请求占比提升到 75%+,如 图1 所示,并将客户端请求平均响应时间 RT 降低了 45%,请求错误率降低了 50%+,视频卡顿率降低了25%+。
图1 客户端 QUIC 请求占比(黄色部分为 QUIC 请求,蓝色部分为其他协议请求)
QUIC 是 Quick UDP Internet Connections 的缩写,意为“快速 UDP 网络连接”。QUIC 由 Google 实现于2013年,是一种网络传输协议,旨在提升网络传输速度。2015年,QUIC 被提交到 IETF,目标是成为下一代的正式网络规范,2018年,HTTP over QUIC 被 IETF 重命名为 HTTP/3。
在 UDP 之上,QUIC 实现了类似 TCP 的丢失重传机制,QUIC 传输以数据包级报头发送,并对每个包增加了单调递增的数据包号来代表传输顺序,当检测到必要帧丢失时,QUIC 会将必要帧绑定到新数据包重发。QUIC 对报文头部和数据也都进行了加密,且建联时改进使用了 DH 密钥交换算法,在防劫持方面也具有一定优势。
所以,QUIC 虽然基于 UDP 实现,但在功能上等价于 TCP + TLS + HTTP/2,除此之外,相较于传统的 HTTP + TCP,QUIC 还具有多项改进网络传输的优势,其部分优势如 图2 所示。
所以,QUIC 虽然基于 UDP 实现,但在功能上等价于 TCP + TLS + HTTP/2,除此之外,相较于传统的 HTTP + TCP,QUIC 还具有多项改进网络传输的优势,其部分优势如 图 2 所示。
图 2 QUIC 表现优势
QUIC 目前分为 gQUIC 与 iQUIC 两种,gQUIC 即为最初的 Google QUIC,而 iQUIC 是后来 IETF 制定的通用传输协议,如 图 3 所示。
图 3 QUIC 分类
相较 iQUIC 而言,gQUIC 目前的应用较为普遍、成熟,如 Cadddy 支持 gQUIC,客户端还有 Chromium 的 Net 库 Cronet 也可以支持 gQUIC,包括 ExoPlayer 等三方库也都提供了对于 gQUIC 的扩展支持。目前来看,选择 gQUIC 对于渴望改善网络传输情况的开发者来说,在接入成本和接入效率上具有优势。
为了快速接入并验证 QUIC,在流量入口设计上,我们选择用 Nginx + Caddy 方案实现;在客户端网络库上,我们选择了 Chromium 的网络库 Cronet,原因如下:
流量入口设计上,由 Nginx 负责处理 HTTP 请求,Caddy 负责处理 UDP 请求,如 图 4 所示。
图 4 流量入口设计
在客户端网络库处理上,如何能保证侵入性最小,风险性最低,并最快接入 QUIC,以验证线上收益,成为了项目的主要目标。
由于客户端用户规模较大,一些量级较小的第三方 QUIC 处理库使用存在一定风险性问题,而自研 QUIC 库支持的成本和效率远远达不到预期,且 QUIC 的网络改善收益对于 Team 来说是未知的,选择一款稳定可靠的网络库来快速实现 MVP 是首要目标,所以在 Android 和 iOS 端,我们都选择了使用 Cronet。
Android 端目前使用的网络库是 OKHttp,为了更清晰有效地验证 QUIC 的效果,我们在接入 Cronet 的同时,也保持了原有网络库 OKHttp 处理逻辑的不变,通过动态配置控制 AB 策略。上线后通过逐渐切量,一部分网络请求走 Cronet,一部分网络请求走 OKHttp,以清晰对比出二者的差异。
而端内目前的两套数据上报机制,一种基于 OKHttp 事件监听实现,一种通过 Hook OKHttp 的事件监听实现,而 Cronet 的上报与 OKHttp 事件回调可以对齐统一,可维护性良好,再次增加了我们选择 Cronet 的理由。
Cronet 在 iOS 接入方面,API 设计友好,基于 NSURLProtocol 通过网络拦截即可实现。iOS 的接入流程可以简单分为初始化 Cronet 和注册 NSURLProtocol 协议两个部分。
在接入 QUIC 时,Android 使用网络库 OKHttp 的拦截器(Intercept)机制来处理网络请求较为便捷,当网络请求从业务顶层通过 OKHttp 传输到 Cronet 对应的拦截器时,会交由 Cronet 处理。
OKHttp 拦截器(Intercept)设计基于责任链模式,由于在拦截器中接入 Cronet 后,网络请求会被 Cronet 库拦截处理,造成后续的拦截器短路,中断 Request 与 Response 信息在拦截器中的传递,所以 Cronet 的拦截处理就应尽量下沉,置于顶层应用拦截器的最底部,如 图 5 所示。
图 5 QUIC 拦截器处理
而对于下层的默认拦截器和自定义网络拦截器都将被 QUIC 短路,默认拦截器中主要提供了缓存、Gzip 解压、建联等处理,其中一部分处理策略由 Cronet 也可以替代实现,而重定向一类策略在 Cronet 监听回调中处理也较为便利。
使用 Cronet 发起请求提供两种实现,一种是 CronetUrlRequest,另一种是符合 HttpURLConnection 标准的,在 CronetUrlRequest 基础上封装了流操作的 CronetHttpURLConnection,由于客户端涉及多媒体场景较多,使用 CronetHttpURLConnection 更能满足大部分需求场景。
目前,我们在视频、图集、列表通用业务网络请求等范围全面实现了 QUIC 的接入。
为了更明显地对比出 QUIC 与 HTTP/HTTPS 之间的数据变化,我们使用了两套网络数据监测机制:全链路统计和 APM,以精准捕获特定请求事件的时间戳,来计算并上报建联时间、响应时间等信息。
通过 Cronet 提供的一些回调,可以实现与 OKHttp 上报监听的逻辑统一。Cronet 提供的 RequestFinishedInfo 可以获得 Request 的建联等信息,提供的 UrlRequest.Callback 可以获得响应等信息,而使用 RequestFinishedInfo 会增加一部分监控逻辑,对 Cronet 有轻微的性能折损,仅使用 UrlRequest.Callback 返回的部分响应信息也足够得出 QUIC 性能数据,全链路上报 OKHttp 与 Cronet 监听对齐方案如 图 6 所示。
图 6 全链路上报事件
由于 Android Cronet 的 CronetHttpURLConnection 在设计上较为封闭,扩展性并不是十分良好,比如响应回调 UrlRequest.Callback,或是内部实现的阻塞队列控制 MessageLooper 都没有暴露给开发者,在此基础上定制一些功能实现,如全链路上报,或是 CronetHttpURLConnection 没有提供的超时时间策略,都不是很方便。
为了解决这个问题,我们建立了一层代理和装饰器,通过拦截 CronetEngine 可以拿到 CronetHttpURLConnection 的 UrlRequest.Callback,对此再做一层包装,由包装类进行统一的事件分发,将 Response 事件分发到了 OKHttp 的全链路处理器中,进而实现了全链路上报设计的统一。
(1)超时时间处理
由于 CronetHttpURLConnection 提供的部分超时时间设置是空实现,而通过 Native Cronet 设置的超时时间也并不符合预期,通过 ConditionVariable 实现超时也未免过于冗余。
CronetHttpURLConnection 内部维护了一个类似于安卓处理管理机制 Message + Looper 的 Task 处理器——MessageLooper,MessageLooper 中维护了一个阻塞队列,并提供了超时的控制策略,通过建立同包名类间接继承 package 访问权限的 MessageLooper,通过反射替换封闭的 MessageLooper 为 NRMessageLooper,进而可以获得超时时间的灵活控制权,在此基础上,我们增加了超时时间的动态下发,以便于风控配置。
对 Cronet 库的定制如 图 7 所示。
图 7 网络库定制架构图
(2)请求占比优化
最初,客户端 QUIC 协议请求占比在统计上报上来的数据观测中并不理想,为了提升 QUIC 占比,我们在与 CDN 共同排查后,优化了双方的处理方案,客户端转用 CronetHttpURLConnection 处理,CDN 侧同时优化处理,并对网络节点也进行了相应调整,保证了两侧方案的统一性,并再次降低了平均响应时间与错误率。
在 iOS 网络库接入方面,调用 Cronet 的 setMetricsEnabled 接口开启 Metric,通过 URLSession 回调获取 networkProtocolName 属性,可以获取当前网络请求的协议类型,以更有针对性地进行数据衡量。
复制代码
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
由于 QUIC 改善的拥塞控制与更低的建联时间,都传递给我们了一个信息:QUIC 对改善视频卡顿等场景有所增益,所以,我们最初的方案选取测试视频域名,而最终视频的卡顿率也有所优化,在弱网场景则更加明显。
由于切量到 QUIC 测试,视频域名需要同步从 HTTP 切换到 HTTPS,这样会增加 QUIC 测试干扰项,为了更好地观察数据,我们设计了 QUIC 的两套切量策略,保持一部分原有域名的请求,并将又一部分原有域名也切换到 HTTPS,而 QUIC 测试将另一部分原有域名切换为 CDN 域名进行测试,切量控制如 图 8 所示。
图 8 QUIC 动态配置
而由于 QUIC 存在较高的未知性,切换网络库对于大量级的网易新闻来说,存在的风险较高,而产生异常对用户影响也极大,所以 Team 采取了 CDN 先预热,后动态下发配置,筛选目标用户进行逐步放量的模式,历经三个版本,完成 QUIC 的测试。
QUIC 立项前开始零零散散的一段调研期,而后 4 月中旬接入,各方 1 周完成敏捷开发,以 MVP 最小可行性原则快速完成功能开发,后续联调、上线测试和逐步迭代改进,持续共计 3 个月左右,以团队预期的速度稳定探索,最终取得了较为理想的测试效果。
通过全链路上报的信息,可以很直观的导出 QUIC 与原有请求传输方式的对比信息,通过放量控制,同一 Host 下,我们使 QUIC 协议请求量与 H2 对齐,其 Count 曲线如 图 9 所示。
(以下图中,蓝色皆为 H2 曲线,黄色皆为 QUIC 曲线)
图 9 请求量对比
得出的 QUIC 与 H2 平均响应时间对比如 图 10 所示,可以直观的看到,QUIC 平均响应时间较 H2 缩减了约 45%。
图 10 平均响应时间对比
而 QUIC 带来的错误率也大幅降低,在错误率表现上同样优秀,如 图 11 所示。
图 11 错误率对比
基于业界的部分研究结论,我们对 QUIC 在弱网的表现也进行了监控。
这里的网络情况以获取到的 手机网络信号强度 LTE 等级 为依据,如下数据观测基于网络信号强度划分的 6 个等级,其中:
同样保持请求量对齐,在该数据观测定义的弱网场景下,QUIC 与 H2 的平均响应时间对比曲线如 图 12 所示。
(以下图中,蓝色皆为 H2 曲线,黄色皆为 QUIC 曲线)
图 12 弱网场景下 RT
在该数据观测定义的一般网络场景下,QUIC 与 H2 的平均响应时间对比曲线如 图 13 所示。
图 13 一般网络场景下 RT
从弱网场景统计曲线的更大差值,可以粗略得出结论,QUIC 在弱网场景下,请求响应时间优化更明显。
在该数据观测定义的弱网场景下,QUIC 与 H2 的错误率对比曲线如 图 14 所示。
图 14 弱网场景下错误率
在该数据观测定义的一般网络场景下,QUIC 与 H2 的错误率对比曲线如 图 15 所示。
图 15 一般网络场景下错误率
从弱网场景统计曲线的更大差值,同样可以粗略得出结论,QUIC 在弱网场景下,错误率优化更明显。
在视频性能方面,我们分别针对卡顿率和 1 秒率进行了监控
从 图 16 可见,QUIC 视频性能表现上,相比 H2 请求,卡顿率下降约为 25%+。
(以下图中,蓝色皆为 H2 曲线,黄色皆为 QUIC 曲线)
图 16 视频卡顿率
这里的 1 秒率 是指视频在 1 秒内达到播放状态的比例,从 图 17 可见,QUIC 在 1 秒率表现上,相比 H2 请求有一定幅度提升。
图 17 视频 1 秒率
对于在 Cronet 的定制和处理方面,还有更多的改进空间,如协商过程的优化,DNS 的定制优化处理等,后续我们会逐步精进,继续深入挖掘 QUIC,期待发现更多的惊喜。
作者简介
李云鹏 2016 年加入网易传媒,目前主要负责架构和性能优化相关工作。QCon 大会讲师,著有《移动开发架构设计实战》等书籍。
李鑫飞 网易新闻客户端 iOS 架构组组长,负责客户端基础架构,组件化及性能优化等工作。
张 鑫 网易资深应用运维工程师,负责网易新闻内容发布系统运维工作,在内容发布、流媒体点播、直播、CDN 运维、全链路监控、大数据运维、业务容器化方面有丰富运维经验。
鸣谢:发起人与项目组其他成员
项目发起人:技术总监 棉明;
Android:技术专家 广丛;
iOS:资深工程师 庞博;
运维及杭研团队等。
领取专属 10元无门槛券
私享最新 技术干货