QUIC 在微博中的落地思考

  • 聂永

2018 年 3 月 16 日

话题:QCon语言 & 开发架构前端

QUIC 致力于成为下一代的互联网传输协议,很自然地,要能解决当前已有的 TCP 协议存在的若干不足之处。

QUIC 的优势

我们先来了解一下 QUIC 协议的几个主要优势:

  1. TCP 协议在建立连接时,需要经历较为漫长的三次握手行为,而在关闭时,也有稍显冗余的 4 次摆手。而 HTTPS 初始连接需要至少 2 个 RTT 交互(添加了握手缓存就会变成了 1-RTT,这里指的是 TLS 1.2),外加 TCP 自身握手流程,最少需要 3 次 RTT 往返,才能够完整建立连接。而 QUIC 协议层面界定了 1-2 个 RTT 握手流程,再次连接为 0-RTT 握手优化流程(但需要添加握手缓存)。
  2. 一旦网络异常,会导致 TCP 出现线头阻塞(Head-of-line blocking),后续数据将被阻塞住;同时针对数据包重传的会导致 RTT 延时测不太准。人们在使用 HTTPS 或 HTTP/2 时,可能因为 TLS 协议层面一个记录(record)丢失,导致后续其它的记录一样会被阻塞住,出现双重阻塞,这基本上是无解的。TCP 丢包阻塞机制导致无法实现真正的多路复用;而 QUIC 协议层面界定了流与流之间独立的特性,就算是丢包,流与流之间也不会互相影响,实现了真正意义上的多路复用机制。
  3. TCP 协议栈被嵌入内核层,导致协议升级迭代操作十分不便,尤其是遗留的中间网络设备和正在服务中的机器;而 QUIC 可以直接部署在用户层,和上层应用程序绑定在一起,随着上层应用程序更新而升级。

这样看来,QUIC 是一个为解决 TCP 存在的缺陷,为未来设计的全新传输层协议。

QUIC 在微博的应用

除了 HTTP/HTTPS 常规连接通道之外,我们想让微博 APP 在初始连接时在网络链路层面请求后端业务时能够再快一点,在弱网环境下尽可能满足用户基本请求。微博 APP 要能够在复杂多变的网络环境下有更多的链路通道选择,增加后端业务的可用性。基于这些考虑,我们将目光对准了 Google QUIC 协议,开始在线上进行尝试性部署、运维支持。

目前大家普遍是在支持 QUIC 的浏览器端(比如 Google Chrome)进行尝试性实践,比如腾讯 QQ 空间等。

微博目前将 QUIC 应用在了移动端业务中,在 Android 和 iOS 版本的微博 APP 中,内置了对 QUIC 的支持。同时通过灰度策略,选择部分用户将常规 HTTP API 请求通过 QUIC 传输。目前大家手机上所安装的微博 APP,只要不是版本太陈旧,都会有 QUIC 依赖库的支持。

除此之外,微博也在尝试在更多业务场景下推广使用 QUIC 协议时积累的优化、线上实践和运维经验,实现整个集团资源共享:

  1. 移动终端 App 环境下,手机端针对 Cronet 做定制和封装,简化兄弟客户端团队 App 调用成本。
  2. 完善的一行命令搞定简化的 QUIC Server 部署解决方案等,包括物理机以及 Docker 虚拟化等。
  3. 兼容来自于 Web 端和移动 Web 浏览器端的 QUIC 业务请求,方便兄弟业务团队快速接入。

微博选择开源的 Caddy 运行 QUIC Server(下面简称为 QUIC Server),在物理机部署环境下采用的是 LVS DR 网络拓扑模式,在实际运维操作过程中遇到以下若干问题。

第一个问题,在部署层面,LVS DR 模式下针对 UDP 支持没有 TCP 完善,Real Server(后面简称 RS)上程序绑定 UDP 端口监听,只能显示的绑定公网 VIP,比如:bind 114.114.114.114:443。而 LVS 需要定时对 RS 进行心跳检测,而心跳走的是 RS 内网 IP,我们直接使用 netcat 搞定 RS 内网 IP 和端口的绑定:

ncat -u -l 10.10.10.10 443 -k -c 'echo "hello"'

这样才算完整。

第二个问题,现实情况是,QUIC Server 要和 Nginx 混合部署,而 Nginx 已经占用 HTTPS 443 端口。为此我们进行了监听端口逻辑的微调:

  • Nginx 继续监听 HTTPS 443 端口,但在所有输出响应的 HTTTP 头部,新增一行内容: Alt-Svc: quic=":443"; ma=2592000; v="38,37,36" ,用于告诉客户端后续请求请切换到 QUIC 通道上
  • 修改 QUIC Server 不再监听 TCP 443 端口,而只监听 UDP 443 端口,只负责 QUIC 请求

这样实现两者协同共存,上帝的归上帝,凯撒的归凯撒,各司其职。

其实若使用 Docker,不对外暴露 TCP 443 端口就可以避免上述问题。

第三个问题,微博 APP 支持 QUIC 协议的初始版本,访问 QUIC Server 一直都是 2-RTT 才能完成握手建连,握手交互频率过于频繁,因此服务端针对整个握手机制做了优化,调整为现在初始 QUIC 连接都走 1-RTT 握手流程,节省了一次链路层往返开销。

第四个问题,QUIC 客户端存在网络制式切换,就算是同一个移动机房,可能第一次业务请求时会落到 A 这台 QUIC Server 实例,后续再次连接,就会落到 B 实例上,重复走 1-RTT 的完整握手流程。为此微博实现了全局级别的握手缓存机制,用户网络发生切换时,下一次的业务请求无论是落到哪一个机房或哪一台实例上,握手建连都会是 0-RTT。

第五个问题,在一些 NAT 网络环境下,UDP 协议会被路由器等中间网络设备禁止,这时客户端 SDK 会直接降级,选择 HTTPS 等备选通道,保证正常业务请求。

我们所遇到问题和挑战,以及困难的解决,跟张旗、史大龙和胡长利等团队成员的共同努力是分不开的,在此也对大家的工作表示感谢。

下面再来看看使用 QUIC 之后的效果。

健康网络情况下,二者区分并不明显。但在弱网环境下,QUIC 要优于 TCP。

1. 在丢包率较高的场景下,QUIC 的接口请求成功率明显优于 HTTPS,在丢包率较低的场景下无明显优势。

2. 在请求耗时方面,丢包率的上升对 QUIC 的影响要远小于 HTTPS。 

3. 在文件传输在有网络延迟的情况下,QUIC 的耗时表现也优于 HTTPS。

4. 针对某一接口的线上数据对比,采用 QUIC 协议的接口请求成功率,比 Original 下的 HTTPS 有 1.01% 的提升。

总之,QUIC 在弱网环境下相比 TCP 有所提升,针对 CDN、流式传输等一些特殊场景也会有一些提升效果;总体上效果不是那么明显,甚至在一些场景下表现还不如 TCP。

近期来看,QUIC 完全取代 TCP 不太可能,但作为一种通道的存在,让我们多了一种链路传输选择。

QUIC 的未来发展

QUIC 致力于成为下一代的互联网传输协议,因为其定位于操作系统用户态,这就意味着在移动终端环境下,具体 APP 可以包含该协议某一个较为完整的演进版,第三方依赖库形式可插拔、可配置,毫无疑问 QUIC 协议的更迭相比 TCP 的修修补补,灵活得多。

目前 QUIC 协议在 IETF 工作组中还处于细节的频繁修改中,IETF 工作组也给出了一些协议实现建议,第三方实现也是百家争鸣。但随之而来的是,人们会面对选择困惑,若要应用于实际,就需要花费很多精力去抉择,是使用 Google QUIC 的实现还是 IETF QUIC 第三方实现,但能够达到线上可运行级别的实现却是凤毛麟角。

随着 TLS 1.3 草案有可能 2018 年定稿为 RFC 技术规范,我们有理由去期待 QUIC 在 2019 年内形成 RFC 技术规范。虽然这并不妨碍我们使用支持某一个 drafts 草案版本 QUIC 实现,但要面对下一个草案是否需要选择支持的困惑。

总之,QUIC 是一款非常伟大的协议,随着自身的逐渐成熟,注定要和 TCP 在相当长的一段时间内并存,相互依赖。

关于作者:

聂永,新浪微博技术专家。从事开发多年,兴趣广泛,前后端都有涉及,包括 Web Page、HTTP API、GraphQL、Erlang、Lua、Golang、Linux Kernel、TCP Server、UDP/QUIC、运维和测试等,也乐此不疲。

工作中擅长写简洁文档,项目接口和 WIKI 基本上会同时展现给团队成员或协作的小伙伴,并努力让自己的行为透明化。

工作业余时间经常为提升团队成员工作效率而努力,为成员技能成长提供针对性建议和帮助。

个人喜好折腾,为公司贡献 5 项技术创新专利,经常写博客

在即将于 4 月 20~22 日举行的 QCon 北京 2018 上,他将分享《QUIC 在手机微博中的应用实践》,详细解释 QUIC 在微博应用的经验。

QCon语言 & 开发架构前端