rrweb 录制存储优化实战:DOM 屏蔽、sampling 抽样、压缩与去冗全指南
2026/9/21 15:35:45 网站建设 项目流程
  • 前端
  • 可观测性
  • 开发工具

【免费下载链接】rrweb

record and replay the web

项目地址:https://gitcode.com/gh_mirrors/rr/rrweb
点击查看免费下载

rrweb 以"录制即事件流"的方式工作,录制数据量与页面复杂度和用户交互频率成正比,在高交互场景下可能远超预期。本文基于官方文档 docs/recipes/optimize-storage.zh_CN.md(英文版见 docs/recipes/optimize-storage.md),系统梳理四类存储优化策略:屏蔽 DOM 元素、sampling 抽样降频、压缩与去冗,并结合 packages/rrweb 与 packages/packer 的源码实现,讲清每个配置项的真实生效机制与默认行为。读完本文,你将能够为你的录制场景组合出一套可落地的存储优化方案。

优化策略整体分为三类:

  • 屏蔽 DOM 元素:减少录制的内容范围;
  • sampling 配置抽样:减少录制的数据量与事件频率;
  • 去冗与压缩:减少数据存储体积。

何时需要优化存储:识别"数据爆炸"场景

在默认配置下,rrweb 会录制全量快照、增量 DOM 变更、鼠标移动/交互、滚动、输入、媒体交互、视口变化等事件流。以下场景最容易产生高于预期的数据量:

  • 长列表:滚动会持续触发大量滚动事件与 DOM 变更;
  • 复杂的 SVG:节点数量大,序列化后体积可观;
  • 包含 JS 控制动画的元素:每一帧都可能产生属性变更或子树变更的 mutation 事件;
  • canvas 动画:逐帧绘制调用(或逐帧截图)本身就是高数据量来源。

这些元素往往不是回放时的关注重点,因此第一层优化就是让 rrweb 直接忽略它们

策略一:屏蔽 DOM 元素,缩小录制范围

blockClass / blockSelector:整棵子树不入镜

在调用record时传入blockClass(默认值为'rr-block')或blockSelector,命中该选择器的元素会被视为"阻塞元素",其整个子树都不会进入录制数据,回放时以占位形式呈现。

import { record } from '@rrweb/record'; record({ emit(event) {}, blockClass: 'rr-block', // 默认值,页面中带 rr-block class 的子树不录制 blockSelector: '.ads, #live-region', // 额外用 CSS 选择器指定要屏蔽的元素 });

从源码看,屏蔽判断发生在快照(snapshot)与增量(incremental)两条链路中。核心实现是 packages/rrweb-snapshot/src/snapshot.ts 中的_isBlockedElement:它支持blockClass为字符串(用classList.contains判断)或正则表达式(对每个 className 做test)。同时在 packages/rrweb/src/record/observer.ts 中,几乎所有事件观察器(鼠标交互、滚动、输入、媒体交互等)在派发事件前都会调用isBlocked做前置过滤,例如initMouseInteractionObserver中对目标元素执行isBlocked(target, blockClass, blockSelector, true)(见 observer.ts)。因此被屏蔽的元素既不会出现在快照中,也不会产生增量事件

ignoreClass / ignoreSelector:保留结构、忽略内容

block不同,ignore(默认 class 为'rr-ignore')表示元素结构仍然会被录制,但元素内部的变化(属性、子树变更)被忽略。典型用途是聊天消息列表、日志面板等高频刷新区域:结构占位保留,内容变化不产生事件。

需要权衡的副作用

屏蔽会带来一个直接后果:被屏蔽区域在回放中不可见。如果该区域是产品核心 UI(例如价格、图表),屏蔽策略会导致回放信息缺失。因此建议只对与业务无关或非关注点的高数据量区域使用,并配合下面的抽样与压缩策略分层使用。

策略二:sampling 抽样:从"频率"维度削减事件

当录制内容无法减少时,可以通过sampling配置降低各类事件的录制频率甚至完全关闭。SamplingStrategy的完整定义位于 packages/types/src/index.ts,下面结合源码逐一说明每个字段的行为与默认值。

示例 1:整体开关与节流阈值

import { record } from '@rrweb/record'; record({ emit(event) {}, sampling: { // 不录制鼠标移动事件 mousemove: false // 不录制鼠标交互事件 mouseInteraction: false, // 设置滚动事件的触发频率 scroll: 150 // 每 150ms 最多触发一次 // set the interval of media interaction event media: 800 // 设置输入事件的录制时机 input: 'last' // 连续输入时,只录制最终值 } })

各字段的真实生效机制(均可在 packages/rrweb/src/record/observer.ts 中找到对应实现):

字段取值默认行为源码实现
mousemovefalse或毫秒数默认节流阈值 50msinitMoveObserver(observer.ts)中sampling.mousemove === false直接跳过注册;为数字时作为位置采集的节流阈值(L118-L119)
mousemoveCallback毫秒数默认 500ms控制"位置批量上报回调"的节流间隔(L120-L123),与mousemove分层节流
mouseInteractionfalsetrue或对象默认录制全部交互initMouseInteractionObserver(observer.ts)中=== false时跳过注册(L200)
scroll毫秒数默认 100msinitScrollObserverthrottle(..., sampling.scroll \|\| 100)(L348)
media毫秒数默认 500msinitMediaInteractionObserverthrottle(..., sampling.media \|\| 500)(L1048)
input'all'|'last'默认'all'(监听inputchangesampling.input === 'last'时只监听change事件,配合cbWithDedup对重复值去重(L466-L484)

几个容易忽视的细节:

  • mousemove: falsemousemoveWait的关系mousemoveWait是旧版参数。在 packages/rrweb/src/record/index.ts 中,当mousemoveWait存在而sampling.mousemove未定义时,会自动迁移到sampling.mousemove,两者等价,建议统一使用sampling
  • input: 'last'的意义:连续输入多字符时只记录最终值,牺牲"逐键回放"的精细度,换取输入事件数量的大幅下降。如果你的回放需要精确还原输入过程,请保留'all'
  • 阈值型参数的取舍scrollmedia等节流阈值越大,事件越稀疏,回放时滚动/媒体进度会呈现"跳跃感",建议在数据量与回放平滑度之间取平衡。

示例 2:细粒度开关鼠标交互类型

当页面中某类交互事件量极大(例如持续聚焦/失焦触发Focus/Blur),可以只关闭特定类型:

import { record } from '@rrweb/record'; record({ emit(event) {}, sampling: { // 定义不录制的鼠标交互事件类型,可以细粒度的开启或关闭对应交互录制 mouseInteraction: { MouseUp: false, MouseDown: false, Click: false, ContextMenu: false, DblClick: false, Focus: false, Blur: false, TouchStart: false, TouchEnd: false, }, }, });

这里的键名与 packages/types/src/index.ts 中MouseInteractions枚举一一对应(还包括TouchMove_DepartedTouchCancel等)。源码中initMouseInteractionObserver会将对象形式的配置转换为disableMap,只有disableMap[eventKey] !== true时才注册对应监听器(observer.ts),从而实现"按需开关"。

其他抽样项:canvas

SamplingStrategy还包含canvas字段:'all'表示逐条录制所有 canvas 绘制调用;设置为 1~60 之间的数字时,将以该频率(每秒最多次数)在 Web Worker 中录制 canvas 截图快照(仅支持OffscreenCanvas的环境可用)。对于 canvas 动画场景,这是比"整元素屏蔽"更精细的折中方案。

策略三:压缩:从"体积"维度削减存储

基于 packFn 的单事件压缩

rrweb 在 packages/packer 中提供了一个基于fflate的简单压缩函数,可以直接作为packFn传入录制配置:

import { pack } from '@rrweb/packer'; record({ emit(event) {}, packFn: pack, });

其底层实现见 packages/packer/src/pack.ts:先JSON.stringify事件,再用zlibSync(基于 fflate 的 deflate/zlib 实现)压缩,最后以二进制安全字符串输出。同时会给事件附加v: 'v1'标记(见 packages/packer/src/base.ts 中的MARK常量),用于回放时识别压缩版本。

从 packages/rrweb/src/record/index.ts 的eventProcessor可以看到,packFn在每个事件通过emit输出前被调用,也就是说每个 event 独立压缩。需要注意:当启用跨域 iframe 录制并向父窗口传递事件(passEmitsToParent)时,事件不会被packFn处理。

回放时,需要把packer.unpack作为unpackFn传入Replayer

import { unpack } from '@rrweb/packer'; import { Replayer } from '@rrweb/replay'; const replayer = new Replayer(events, { unpackFn: unpack, });

unpack的实现(packages/packer/src/unpack.ts)具备较强的健壮性:先尝试按普通 JSON 解析(兼容未压缩的历史数据),再尝试按压缩数据解压,并通过v === MARK校验版本兼容性,最后才抛出Unknown data format错误。这意味着你可以混合使用压缩与未压缩的数据,只要数据本身格式合法。

批量压缩:更推荐的服务端方案

基于packFn的单事件压缩以每个 event为单位进行,这往往不能发挥 rrweb 录制数据易于压缩的优势——相邻事件之间(尤其是连续的全量快照、样式表、长文本输入)存在大量重复片段,而单个小 JSON 的 zlib 压缩无法利用跨事件的冗余。

因此文档更加推荐在服务端实现多个 event 的批量压缩,例如将单次用户操作(一个 session / 一次请求上下文)产生的所有 event 合并为一个大块再压缩。对于 gzip、zlib 等压缩算法而言,更大的输入块意味着更高的压缩比,这在长会话场景下收益显著。

策略四:去冗:只存一份"相同的数据"

压缩之外,另一个优化思路是去冗(deduplication)

为了在回放中准确模拟 hover 等效果,rrweb 会尽可能地将 CSS 样式inline进录制数据(即inlineStylesheet: true,这是 packages/rrweb/src/record/index.ts 的默认值)。可以想象,如果使用 rrweb 录制大量用户对同一站点的访问,每个 session 的录制数据中都会保存几乎完全相同的样式表内容——N 个用户就是 N 份拷贝。

去冗的基本思路:

  1. 遍历录制数据,识别包含样式表内容的事件(例如快照中的stylesheet数据、增量中的样式变更);
  2. 将这部分内容提取出来单独保存一份(按内容哈希去重);
  3. 回放时再通过映射关系把去冗后的内容还原回事件中。

同样的思路也适用于全量快照(FullSnapshot):不同 session 的首次全量快照如果来自同一页面骨架,其 DOM 结构大部分相同,可以跨 session 提取公共部分仅存储一份,显著降低存储总量。

组合应用:一套可落地的优化路线

四类策略并非互斥,实际项目中建议按"先减量、再减频、后减体积"的顺序组合:

  1. 屏蔽:对长列表、SVG、JS 动画、canvas 动画等"数据大户"用blockClass/blockSelector/ignoreClass排除出录制范围(packages/rrweb-snapshot/src/snapshot.ts 是判断的核心);
  2. 抽样:用sampling关闭或降频次要事件——mousemove: falsemouseInteraction细粒度关闭、scroll/media调大节流阈值、input: 'last'(各观察器实现在 packages/rrweb/src/record/observer.ts);
  3. 压缩:客户端可用packFn(packages/packer/src/pack.ts)做兜底;服务端对整段 session 批量压缩以获得更优压缩比;
  4. 去冗:在存储层对样式表与全量快照做内容级去重。

每一步优化都会带来回放精细度或实现复杂度的代价:屏蔽会导致部分区域回放不可见、input: 'last'会丢失逐键过程、批量压缩需要在回放前先整体解压。建议在接入时先评估真实数据量分布(哪些事件占比最高),再有针对性地启用对应策略,并在每个阶段用真实录制数据验证回放效果,最终得到数据量与回放质量的最优平衡点。

  • 前端
  • 可观测性
  • 开发工具

【免费下载链接】rrweb

record and replay the web

项目地址:https://gitcode.com/gh_mirrors/rr/rrweb
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询