Rejouer:探秘web页面录制与回放的新大陆

442次阅读  |  发布于3年以前

在我长期的固有认知中:为了收集和上报网站交互过程中 JavaScript 的报错信息和其它相关数据,我们一般会采用自研或者第三方的SDK,也可以简单理解为埋点。这也是为了方便生产问题的排查,做到可溯源。

但是,在前一段,和隔壁组的同事交流时,我发现了一个有点东西的系统--Rejouer。这个系统可以做到完整复现用户的操作行为,类似于录屏的功能,起初我还真的以为他们就是给我放了一个录屏,后来发现没那么简单。

本文也是在和这位同事探讨的过程中得到的一些启发。

业务背景

不要重复造轮子!这是我一直以来的信念。轮子的出现必定是要为业务服务。那么Rejouer出现的背景是什么呢?

痛点

我们来回顾一下日常处理线上问题的场景:

上面图片中的是一个正常处理线上问题的流程。我们一般会通过之前埋入的一些业务打点,根据用户uid去追溯问题,这样基本可以解决大部分的线上问题。但在实际的业务场景中,总是会出现一些奇奇怪怪的问题:

除了上面提到的这些问题,还有一个影响线上问题解决速度最致命的:链路过长!!

由此可见定位问题的痛点也是足够多。

期望的结果

上面提到了那么多的业务痛点,那么为了解决这些问题,我们肯定期望能有这么一个系统:

技术调研

有了上面的需求,下面让我们来做下技术调研,看如何进行实现。

初期的调研,探寻了 5 种可行的方案:

我们一一来进行分析。

webRTC

首先简单了解一下webRTC:它是一套客户端点对点流式信息传播的技术方案。

其不仅可以用于「音视频录制、视频通话」,还可以用在「照相机、音乐播放器、共享远程桌面、即时通信工具、P2P 网络加速、文件传输、实时人脸识别」等场景上。

但我们来看下它的兼容性情况:

不支持 IE,这个就有点不太友好了,而且是完全不支持(毕竟我们还是有部分的用户在使用IE浏览器的 )

除了兼容性之外,还有另外一个问题:webrtc在启动能力前,会有原生的chrome窗口弹出来询问用户,且在开启getDisplayMedia之后会有一个状态条,所以在整个的操作中会有一些生硬,用户有明显的知道自己的屏幕正在被录制。

显然针对这种场景,更适合用在测试场景下,或者明确授权情况下。也不符合我们的需求。

Puppeteer

puppeteer 是谷歌官方出品的一个通过 DevTools 协议控制 headless ChromeNode 库,我们可以通过 puppeteer 提供的 API 直接控制 Chrome,进而模拟大部分用户在浏览器上的操作,来进行 UI Test 或者作为爬虫访问页面来收集数据。

puppeteer主要的问题是它强依赖于Chromium,客户操作也较为复杂。

因此也不是太符合我们的设定。

浏览器插件

简单来说:浏览器插件就是一个用Web技术开发、用来增强浏览器功能的软件。

正如其名,由于浏览器厂商多样性,其开发适配成本也不低。同时还伴随着诸如权限、培训使用等问题。

也不符合我们的需求。

html2Canvas

利用Canvas截图,使用 html2Canvas 库,不停的画页面然后不停的截图,再将图片组成视频播放出来。

这种也是我首先想到的方案,不过在脑海里没有停留多久,就被否决了:性能太差了!!

而且还存在诸如图片跨域兼容性问题。

好家伙,找了这么久,一个能用的都没有。把我都给整不会了

冷静的分析一波:其实还有一个 API:MutationObserver。该接口提供了监视对 DOM 树所做更改的能力。我们可以利用这个接口,保存每次变化的DOM数据,并把这些数据转换成可视化的数据结构,然后分别保存起来。接着使用特定的方式对之前保存起来的DOM数据进行还原并重新渲染出来。DOM节点的变化也就意味了页面轨迹发送了变化。这样就可以把这些轨迹记录下来。

理想很丰满,现实真的挺残酷!

真正自己去落地时还是遇到很多问题的,包括其中的很多细节,那么类似的社区里面有没有开源的解决方案呢?

你别说,还真有:rrweb

rrweb

打开rrweb[1]官方地址:

诚如所言:rrweb 是一个开源的 Web 会话回放库,它提供易于使用的 API 来记录用户的交互并远程回放。

再看首页右侧的录制回放演示。

这不就是我们想要的吗?

体验了一波之后,我感觉rrweb基本能满足我们的业务诉求了:

关于rrweb的基本使用,这里不会赘述,可参考rrweb 官方使用指南[2]

rrweb内部做了什么?

回想我们起初做的一些调研,都没有成功落地。最终Rejouer的落地也是基于rrweb做了很多适配自身业务的封装。

本文后半部分更多是来探讨rrwebRejouer由于是内部项目,更多细节暂时不便透露

对于rrweb内部的一些实现,我们还是比较好奇的。下面我准备深入去挖掘他的内部实现细节。

既然要分析它的内部运作流程,那么有几个前置的知识点需要提前同步一下:

DOM 快照

⻚⾯中的视图状态可以通过 DOM 树的形式描述,所以当我们尝试录制⼀个⻚⾯时,我们实际上是在记录 DOM 树在各个时间点上的状态,在 rrweb 中我们称⼀次这样的状态记录为⼀个快照。

定时快照

定时快照的概念相对比较好理解:定时对⻚⾯制作快照完成录制。

但有很大的弊端:

所以产生了增量快照的概念。

增量快照

其实我们可以只在页面初始化完成之后 clone 一次完整的页面内容,等到页面有变动的时候,只记录变化的部分。这样一来,好处就显而易见了:

这里提到了只记录变化的部分,那这个变化又该怎么去衡量呢?

我们可以暂且把这种变化称为视图变更

视图变更

引发视图变更的操作大致有:

这里的DOM 变动我们可以采用MutationObserver来监听,它会以批量的方式返回 dom 的更新记录

但是它没法跟踪像 inputtextareaselect 这类可交互元素的输入。

对于这种可交互的元素,我们主要靠监听 inputchange 来记录输入的过程。

但是有些元素的值是通过程序直接设置的,这样是不会触发 inputchange 事件的。这种情况下我们可以通过劫持对应属性的 setter 来达到监听的目的。

ok,有了这些知识,下面我们就来看一下rrweb的运行流程吧。

运行流程

这里可以简单用一张图来概括:

DOM 加载完成后,record 会做一次完整的 DOM 序列化,我们把它叫做全量快照,全量快照记录了整个 HTML 数据结构。

然后在页面发生视图变动时(DOM 变化和用户操作),记录增量数据,完成页面的录制,然后保存到远程服务器。

回放时会先创建沙箱环境,接着重建全量快照,在通过 requestAnimationFrame 模拟定时器的方式来播放增量快照。

总结

本文主要整理了在后埋点时代针对业务中一些难复现、易引起客户纠纷的一些场景,在页面录制/回放道路上的探索过程。探讨了几种解决方案的利弊,最终也是基于社区非常优秀的rrweb做的封装。同时也分析了一下rrweb的核心实现。关于rrweb更多细节的探讨可参考rrweb:打开 web 页面录制与回放的黑盒子[3] 。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8