基于rrweb的会话录制:后端可靠性与API调用优化问题
解决方案:rrweb会话录制后端优化与API调用缩减
核心问题:减少API调用且不丢失事件
采用双阈值批量上传策略,同时控制事件数量和时间间隔,平衡调用频率与事件完整性:
客户端实现逻辑
- 设置两个触发条件:当缓存事件达到
MAX_EVENTS(比如20个),或距离上次上传超过UPLOAD_INTERVAL(比如10秒),满足任一条件就触发上传。 - 页面卸载时用
navigator.sendBeacon上传剩余事件,该API由浏览器保证会尽力完成请求,不会被页面关闭/跳转中断。 - 上传失败时自动重试,把未成功上传的事件放回缓存,避免丢失。
示例代码:
let eventBuffer = []; let uploadTimer = null; const MAX_EVENTS = 20; const UPLOAD_INTERVAL = 10000; // 10秒 function addEvent(event) { eventBuffer.push(event); // 数量阈值触发上传 if (eventBuffer.length >= MAX_EVENTS) { uploadEvents(); } // 重置时间阈值定时器,无操作时到点自动上传 if (uploadTimer) clearTimeout(uploadTimer); uploadTimer = setTimeout(uploadEvents, UPLOAD_INTERVAL); } async function uploadEvents() { if (eventBuffer.length === 0) return; const pendingEvents = [...eventBuffer]; eventBuffer = []; try { await fetch('/api/saveEventsToBackend', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: '当前会话ID', events: pendingEvents }) }); } catch (err) { // 上传失败,回退缓存并重试 eventBuffer = [...pendingEvents, ...eventBuffer]; setTimeout(uploadEvents, 2000); // 2秒后重试 } } // 页面卸载时上传剩余事件并标记会话完成 window.addEventListener('beforeunload', () => { if (eventBuffer.length === 0) return; const finalData = JSON.stringify({ sessionId: '当前会话ID', events: eventBuffer, isFinal: true }); navigator.sendBeacon('/api/saveEventsToBackend', finalData); });
会话活跃状态与终止时机处理
客户端侧
- 监听页面
visibilitychange事件,页面从隐藏切换为显示时,正常按双阈值策略上传事件即可维持会话活跃。 - 无操作时,时间阈值自动触发上传,后端通过更新会话最后活跃时间识别会话仍在活跃。
后端侧(Go+Beego+PostgreSQL)
- 给每个会话记录
last_active_at时间戳,每次收到事件时更新该字段。 - 用全局定时任务扫描超时会话,替代给每个会话单独启动goroutine,减少资源消耗:
- 每隔5秒扫描所有状态为
active的会话,若last_active_at距离当前超过10秒,标记为timeout终止。 - 收到客户端
isFinal: true的请求时,直接将会话标记为finished。
- 每隔5秒扫描所有状态为
示例代码:
// 会话模型 type Session struct { ID string `orm:"pk;column(session_id)"` LastActiveAt time.Time `orm:"column(last_active_at);auto_now"` Status string `orm:"column(status);default:active"` // active/finished/timeout } // 保存事件接口 func (c *RecordController) SaveEvents() { var req struct { SessionId string `json:"sessionId"` Events []interface{} `json:"events"` IsFinal bool `json:"isFinal"` } if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil { c.Abort(400, "invalid request body") return } o := orm.NewOrm() session := Session{ID: req.SessionId} if err := o.Read(&session); err == nil { session.LastActiveAt = time.Now() if req.IsFinal { session.Status = "finished" } _, _ = o.Update(&session, "LastActiveAt", "Status") } // 批量插入事件到PostgreSQL(示例逻辑) // ... c.Data["json"] = map[string]string{"status": "success"} c.ServeJSON() } // 初始化定时任务 func init() { // 每5秒执行一次超时检查 beego.AddTask("0 */5 * * * *", func() { o := orm.NewOrm() var sessions []Session timeoutTime := time.Now().Add(-10 * time.Second) // 查询活跃且超时的会话 _, _ = o.QueryTable("session"). Filter("status", "active"). Filter("last_active_at__lt", timeoutTime). All(&sessions) for _, s := range sessions { s.Status = "timeout" _, _ = o.Update(&s, "Status") } }) }
关键优化点总结
- 双阈值上传:既避免用户操作频繁时API调用过多,也防止无操作时事件长期积压。
- 页面卸载用
sendBeacon:保证最后一批事件不会丢失,准确标记会话完成。 - 全局定时扫描:替代单个会话的goroutine,减少后端资源消耗,高效处理超时终止。
- 上传失败重试:防止网络波动导致的事件丢失。
内容的提问来源于stack exchange,提问作者scaryhamid
相关产品推荐
相关产品推荐

