You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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。

示例代码:

// 会话模型
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")
    }
  })
}

关键优化点总结

  1. 双阈值上传:既避免用户操作频繁时API调用过多,也防止无操作时事件长期积压。
  2. 页面卸载用sendBeacon:保证最后一批事件不会丢失,准确标记会话完成。
  3. 全局定时扫描:替代单个会话的goroutine,减少后端资源消耗,高效处理超时终止。
  4. 上传失败重试:防止网络波动导致的事件丢失。

内容的提问来源于stack exchange,提问作者scaryhamid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:57:29