在知乎上线了创作中心后,我才知道在我的回答中阅读量最大的居然是之前youtube和b站的性能对比,那篇回答只是做了非常粗浅的介绍,感觉有点对不起这个阅读量。实际上在团队内部我是进行了《逐帧分析youtube》的分享,这次也把一些内容梳理成文分享给大家吧。
接下来我会逐个请求分析youtube究竟使用那些国内大家不那么常用的技术,因为点会比较多我在正文中就不一一详细介绍了但都会配上相关阅读连接,我只会对与带来关键性能提升的部分做展开分析。
- Part1 网络请求分析
- Part2 使用技术分析
- Part3 性能分析
Part1 网络请求
协议
http/2 + quic/46
从截图可见youtube在html文档和静态资源请求上都启用了http/2 + quic/46,http/2相信还有比较多人已经开始尝试使用了,但quic在生产环境使用的并不多见。QUIC(Quick UDP Internet Connection)是google自spdy后推出的新的协议,从名字就能看到它最大的区别是使用UDP传输协议,这可以说是对http/2是一个关键的补充,因为在现实世界中我们会遇到http/2队头阻塞问题,在丢包比较严重的环境下http/2的性能是会有所下降的,而在网页这种多请求并发的情况下由于http/2的多路复用(同一个域名下共享一个tcp)的情况下http/2的整体性能甚至会比http1.1更差,而quic在解决了tcp的队头阻塞的同时实现了0RTT数据传输,连接迁移等新特性。
用最直观的感受来解释就是使用quic后网站的性能提升15%,弱网环境提升20%,如果你的用户会频繁的wifi 4g之间切换或者经常乘坐高铁、地铁高速移动,quic让他们ip变更时不要重新建联,这对视频网站来说是非常有意义的提升。
https://www.cnblogs.com/jb2011/p/8458549.html
http://www.52im.net/thread-1309-1-1.html
html http header
Referrer Policy
- 首页 :no-referrer-when-downgrade (浏览器默认)
- 播放页:origin-when-cross-origin
首页的设置是浏览器默认设置我们就不需要讨论了,但播放页的设置不同origin-when-cross-origin的目标是保护隐私,因为播放页是带参数的,为了避免被下一跳的第三方页面知道用户是从哪个视频内容发生的跳转所以只返回host信息
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referrer-Policy
alt-svc: quic=":443"; ma=2592000; v="46,44,43,39"
alt-svc的全名是HTTP Alternative Services,服务端可以将自己的替代服务地址以协议规定的方式告诉浏览器,对于支持这个协议的浏览器来说,后续请求都会使用新地址。除了提供替代服务外,理论上我们还能通过这个字段的设置来引导用户访问不同的多语言版本或者abtest版本哦。
https://imququ.com/post/http-alt-svc.html
content-encoding: br
与常见的通用压缩算法不同,br(Brotli)使用一个预定义的120千字节字典。该字典包含超过13000个常用单词、短语和其他子字符串,这些来自一个文本和HTML文档的大型语料库。
https://zh.wikipedia.org/wiki/Brotli
https://tools.ietf.org/html/rfc7932 (你是不是很好奇这120千字节的字典里有什么?)
strict-transport-security: max-age=31536000
通常简称为HSTS是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源,而不是HTTP。
https://developer.mozilla.org/zh-CN/docs/Security/HTTP_Strict_Transport_Security
x-content-type-options | x-frame-options | x-xss-protection
安全响应头
https://imququ.com/post/web-security-and-response-header.html
JS http header
timing-allow-origin: www.youtube.com
允许哪个域名可以访问当前资源的Resource Timing API提供的相关信息。
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Timing-Allow-Origin
html http header
我们可以看到第五个请求是一个html文档,它是通过一个比较有趣的方式引入到页面当中的:import html
developer.mozilla.org/zh-CN/docs/… 已不建议使用
CSS http header
link: fonts.gstatic.com; rel=preconnect; crossorigin
css请求header中完成预连接算是相对少见的做法,但在css内部引用了较多非当前域名资源的情况下在http header里声明预连接会比在当前页面或前置页面用通过<link perconnect>要更通用一些,因为你不需要考虑前置页面都有哪些。
https://www.w3.org/TR/resource-hints/#preconnect
https://www.cdnplanet.com/blog/faster-google-webfonts-preconnect/
IMG http header
access-control-expose-headers: Content-Length
允许访问Content-Length,应该是为了做客户端资源加载速度的分析而提供的信息。
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
content-disposition: inline;filename="unnamed.jpg"
inline声明图片应该被行内显示而不是作为附件下载,filename是下载时使用的默认名称
https://www.rfc-editor.org/rfc/rfc1806.txt
AJAX http header
P3P:个人隐私安全平台项目(The Platform forPrivacy Preferences Project)的标准
- p3p: policyref="https://www.googleadservices.com/pagead/p3p.xml", CP="xxxxxx"
- p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=en for more info."
Part2 使用技术介绍
Web Animations api
允许同步和定时更改网页的呈现, 即DOM元素的动画。它通过组合两个模型来实现:时序模型 和 动画模型。
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API (api介绍)
https://github.com/web-animations/web-animations-js (实现)
https://github.com/web-animations/web-animations-next (polyfill)
Web Components & Polymer
允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。
https://developer.mozilla.org/zh-CN/docs/Web/Web_Components
https://github.com/webcomponents/webcomponentsjs
requestIdleCallback & cancelIdleCallback
会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟敏感的事件产生影响。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/cancelIdleCallback
SPF.js
一个轻量级的JS框架,用于把youtube改造为一个单页应用。
https://youtube.github.io/spfjs/
https://github.com/youtube/spfjs
https://www.youtube.com/watch?v=yNe_HdayTtY
在一次页面跳转的过程:<a href = "feed/trending"> 为普通连接,点击后被network.js劫持后发出为feed/trending?pbj=1的请求
在返回头中有如此信息:x-spf-response-type:multipart
返回的正文从html变成了一个json对象,包含了渲染对应模块所需的数据。
同类实现算是比较多的,facebook早年的bigpipe,微博都有使用类似的技术减少连续访问时相同模块的重复渲染浪费。
Part3 性能分析
上面科普了挺多的技术点,或多或少都跟性能有些关系。这部分请跟随我的视角来看看性能方面youtube还做了哪些优化。
内联脚本与CSS
查看页面源代码我们可以看到youtube的html是做了SSR的,但并没有做data→dom的这一步,因此当中不仅有模块初始化的JS、CSS代码外,还有包含关键数据的JSON。观察下来youtube页面基本遵循了一个规律,有共性需要的js、css才做外联,当前页面所必须的js与css做内联。可能一部分同学会质疑这个做法与曾经的优化建议里说的不一样,经过B站自己的实验证明,在html文件体积可控的情况下把页面必须的js、css内联,首屏的渲染速度会有较大的提升,其收益主要来自减少了http建联的时间。在用户真实的使用场景当中,每有一个请求在播放器初始化之前,哪怕是一个http204的请求都会对我们视频首帧播放的8分位时间有50-100ms的影响。
另外,我们还发现一点是youtube对播放的依赖进行了较好的治理,大家可以尝试把红框内的所有请求都block掉,再刷新页面
你会神奇的发现,虽然样式有一些错乱,播放器意外的模块加载失败,但播放器与视频依然保持正常运作。虽然这不是正常用户的行为,但能做到这一点并关联上文中提到的requestIdleCallback & cancelIdleCallback的空闲时执行的调度能力,我们知道虽然这些脚本的加载在播放器之前,但是他们的执行时机完全可以被后置,最终实现播放器的优先渲染。
从连续截图中我们也可以印证这一点
如果我们对xhr等数据请求做同样的处理,我们还能印证另外一点,就是首屏渲染的所有数据都已经在SSR中提供了,不需要额外的请求接口。
SVG代替icon
从页面源代码我们还能看到另外一点是youtube的icon全都是用svg实现的,这是和他们采取的扁平化的设计是分不开的,而内联的svg不仅仅带来了体积小、连接数少的好处,如果你仔细观察你还会看到大部分的icon都是有小动效的,这些动效是依靠svg动画实现的,比传统的gif或者序列帧动画体积可要小太多了。
SPA
youtube的设计几经修改后最后固化为当前我们熟悉的版本,在不同的页面跳转过程中顶部和侧边栏菜单是作为常驻模块存在,只需要渲染右侧的content。对于大多前端来说新做一个spa项目是相对容易的,而对于youtube这个庞大的网站而言通过重构把多页面重构为单页面的代价远大于收益,因此他们是通过spf.js框架来解决这个问题的,劫持全局A链接的点击事件,只请求下一个页面渲染所需的数据在当前页面完成渲染,避免了大量无意义的http建联和模块重新渲染的开销,加快了页面渲染的速度。
而在视频连续播放的场景中,播放器的初始化也是一个巨大的开销往往要需要400-600ms来完成,在spf.js的加持下播放器不需要重新初始化只需要载入下一个视频的数据即可。如果你觉得体验youtube比较困难的话,你可以来b站试试,访问右侧的相关推荐我们同样重用了页面和播放器,此时从点击卡片到视频能够播放只需要500ms不到的时间。
跨页面缓存
通过上图可以看到youtube的静态资源是有冗余部分的而且体积相当的大(300k的js 600k的import html),并没有按页面维度进行最小化的打包,这应该是为了做成spa后下一个页面的渲染不需要请求更多的资源即可完成,从单个页面加载速度来看这显然是不划算的,不知道youtube是不是经过自己的实验证明了在用户连续访问的场景中这样做的整体收益更大呢?
WebM/vp9 → AV1
视频网站的关键速度是首帧时间,影响这个时间的除了我们前面分析的页面加载顺序和资源优化外,很重要一点就是视频格式,而这当中youtube的魔法是真的多。在youtube的分享中我们可以看到目前youtube主要使用的是vp9,它只需要h.264一半的体积就能提供相同的画质
https://www.youtube.com/watch?v=xo_R40C7RTo
另一项魔法如上图,我们可以看到一帧画面被分割成了不同尺寸的格子,youtube对此进行了自动量化的实验,目的是对于视觉敏感的区域输出更多的细节,对于不敏感的区域降低细节。
vp9的窄带高清的能力,在全球范围内为youtube带来了非常可观的增长。在你可能因为vp9感到兴奋时,youtube去年在vp9的基础上又再推出了av1,av1比vp9体积又减少了30%。
未来在移动设备上还有会有原生支持。
末尾
我们来回顾一下从分析youtube代码我们得到了哪些优化建议:
- 协议层面尽快升级到http/2+quic(http/3),会为我们带来15%性能提升,并且可以让挤地铁的同学有更好的视频播放体验。
- 压缩格式br,可以更有效的压缩html、js、css
- 对于多页面公用的css和js,你可以通过在http header中增加link perconnent,让文件内引用的其他域名获得加速,而不需要在html新增标签覆盖面更广
- 合理使用requestIdleCallback & cancelIdleCallback可以让你的代码执行减少拥堵。
- 如果你需要把一个多页面站点改造成单页应用又不想付出高昂的代价,可以尝试考虑spf.js的方案(但需要服务端配合哦!)
- 在首个请求体积可控的情况下内联必须的JS和CSS会让你获得更快的首屏时间(根据我们实验结果建议<200k)
- 采用svg实现icon,不仅体积小还可以实现动画,比base64和iconfont要更灵活。(需要设计配合)
- 尽可能能的重用页面资源和已渲染的模块,避免任何一点不必要的浪费。
- vp9和av1比国内常见的h.264有明显的优势,而且不像h.265要收取高额的费用。
最后补充一点:Node ssr是好东西,有了它前面几点才能在团队的闭环里完成不需要求人。
广告时间
去年在对youtube进行完逐帧分析后,b站的小伙伴们也是卯足了劲做优化做了大量的实验和技术升级,如果你也想知道一个禁用缓存的1080p视频如何做到从进入页面到画面播放1秒都不到的话,请留意6月20日举办的GMTC2019大会,届时将会由我的同事冷鬼为大家带来《B站的视频体验进化之路》
https://gmtc2019.geekbang.org/presentation/1542