23、WebRTC日志与监控:Stats API的使用、自定义指标上报、告警与自动恢复机制

做实时通信,最怕什么?

不是代码写崩了,而是用户那边卡成幻灯片,你这边却两眼一抹黑。我见过太多团队,上线前跑得飞起,上线后用户投诉不断,结果连个日志都捞不到。说白了,没有监控的WebRTC,就像闭着眼睛开车

这一章,我们就来聊聊怎么给WebRTC装上「仪表盘」和「报警器」。我会把我在生产环境中踩过的坑、总结的经验,全都摊开来讲。

核心观点:WebRTC的监控不是「锦上添花」,而是「保命符」。没有Stats API,你连问题出在哪儿都不知道。

23.1 Stats API:WebRTC的「黑匣子」

WebRTC内置了一套非常强大的统计接口——RTCPeerConnection.getStats()。它就像飞机上的黑匣子,记录了每一帧、每一个包、每一次抖动的细节。

我个人习惯,在每次通话建立后,立即启动一个定时器,每隔1秒拉取一次Stats数据。为什么是1秒?太频繁会影响性能,太稀疏又抓不住瞬时抖动。

// 基础用法
const pc = new RTCPeerConnection(config);

// 启动定时采集
const statsInterval = setInterval(async () => {
  const stats = await pc.getStats();
  stats.forEach(report => {
    if (report.type === 'inbound-rtp' && report.kind === 'video') {
      console.log('视频接收端统计:', {
        packetsLost: report.packetsLost,
        jitter: report.jitter,
        framesDecoded: report.framesDecoded,
        roundTripTime: report.roundTripTime
      });
    }
  });
}, 1000);

// 通话结束时清理
pc.onconnectionstatechange = () => {
  if (pc.connectionState === 'closed') {
    clearInterval(statsInterval);
  }
};

小技巧:不要只采集一次。我建议至少采集整个通话周期的数据,这样事后分析问题时,才能看到「从流畅到卡顿」的完整演变过程。

23.2 关键指标解读:哪些数据值得盯?

Stats API返回的数据很多,但真正需要关注的,其实就那么几个。我把它分成三类:

指标类别 关键字段 正常范围 告警阈值
网络质量 roundTripTime, packetsLost, jitter RTT < 100ms, 丢包率 < 1% RTT > 300ms, 丢包率 > 5%
编码性能 framesPerSecond, framesDecoded, encodeTime FPS > 24, 编码耗时 < 10ms FPS < 15, 编码耗时 > 30ms
传输状态 bytesSent, bytesReceived, nackCount 带宽利用率 60%-80% NACK 激增 > 100次/秒

嗯,这里要注意:丢包率不是越低越好。我曾经遇到一个项目,丢包率一直为0,结果发现是Stats API采集错了方向——采集的是本地发送端,而不是接收端。接收端的丢包率才是真实体验。

23.3 自定义指标上报:把数据送到该去的地方

光在浏览器控制台看没用,你得把数据上报到后端。我一般会设计一个「上报器」,把Stats数据格式化后,通过HTTP或WebSocket发送到监控平台。

class StatsReporter {
  constructor(sessionId, userId) {
    this.sessionId = sessionId;
    this.userId = userId;
    this.buffer = [];
    this.flushInterval = 5000; // 每5秒批量上报一次
    this.startFlush();
  }

  push(report) {
    this.buffer.push({
      timestamp: Date.now(),
      sessionId: this.sessionId,
      userId: this.userId,
      ...report
    });
  }

  async flush() {
    if (this.buffer.length === 0) return;
    const payload = this.buffer.splice(0);
    try {
      await fetch('/api/stats/batch', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });
    } catch (err) {
      console.warn('上报失败,数据暂存本地', err);
      // 失败时不要清空buffer,下次重试
      this.buffer.unshift(...payload);
    }
  }

  startFlush() {
    setInterval(() => this.flush(), this.flushInterval);
  }
}

// 使用示例
const reporter = new StatsReporter('session-001', 'user-123');
// 在getStats回调中调用 reporter.push(...)

避坑指南:我曾经因为上报频率太高,直接把后端打挂了。建议:1) 批量上报,不要每条数据都发一个请求;2) 设置上报失败的重试机制,但要有最大重试次数,防止死循环;3) 上报数据要带上时间戳,方便后端做时序对齐。

23.4 告警机制:别等用户投诉才知道

数据采集上来了,接下来就是「什么时候该报警」。我总结了一个三层告警模型:

  1. 瞬时告警:单次采样超过阈值。比如RTT突然飙到500ms,立即触发。
  2. 持续告警:连续N次采样都超过阈值。比如连续5秒丢包率都超过5%,说明问题持续存在。
  3. 趋势告警:指标在持续恶化。比如RTT在30秒内从50ms涨到200ms,虽然还没到阈值,但趋势很危险。

我个人最看重的是「趋势告警」。为什么?因为瞬时抖动可能是网络波动,但趋势恶化往往意味着「要出大事了」。我在一个视频会议项目中,就是靠趋势告警提前10秒发现了网络劣化,及时触发了码率降级,保住了通话质量。

// 趋势告警示例:检测RTT是否持续上升
class TrendDetector {
  constructor(windowSize = 10) {
    this.window = [];
    this.windowSize = windowSize;
  }

  addSample(value) {
    this.window.push(value);
    if (this.window.length > this.windowSize) {
      this.window.shift();
    }
    return this.detectTrend();
  }

  detectTrend() {
    if (this.window.length < 5) return false;
    // 简单线性回归判断斜率
    const n = this.window.length;
    const sumX = n * (n - 1) / 2;
    const sumY = this.window.reduce((a, b) => a + b, 0);
    const sumXY = this.window.reduce((acc, y, x) => acc + x * y, 0);
    const sumX2 = this.window.reduce((acc, x) => acc + x * x, 0);
    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
    return slope > 5; // 斜率大于5,认为在快速恶化
  }
}

23.5 自动恢复机制:让系统自己「止血」

告警只是第一步,更重要的是「自动恢复」。你不能让运维半夜爬起来手动降码率。我设计了一套分级恢复策略:

告警级别 触发条件 自动恢复动作
L1 - 轻度 丢包率 3%-5% 降低视频分辨率(720p → 480p)
L2 - 中度 丢包率 5%-10% 或 RTT > 300ms 降低帧率(30fps → 15fps)+ 启用FEC
L3 - 重度 丢包率 > 10% 或 RTT > 500ms 关闭视频,仅保留音频
L4 - 致命 连续10秒无任何数据 触发ICE重启,尝试重建连接
// 自动恢复控制器
class AutoHealer {
  constructor(pc) {
    this.pc = pc;
    this.currentLevel = 0;
  }

  async heal(metrics) {
    const level = this.evaluate(metrics);
    if (level === this.currentLevel) return;

    this.currentLevel = level;
    switch (level) {
      case 1:
        await this.setVideoConstraints({ width: 854, height: 480 });
        break;
      case 2:
        await this.setVideoConstraints({ frameRate: 15 });
        await this.enableFEC(true);
        break;
      case 3:
        await this.disableVideo();
        break;
      case 4:
        await this.restartICE();
        break;
    }
    console.log(`自动恢复:已执行 L${level} 恢复策略`);
  }

  evaluate(metrics) {
    if (metrics.packetsLost > 10) return 3;
    if (metrics.packetsLost > 5 || metrics.rtt > 300) return 2;
    if (metrics.packetsLost > 3) return 1;
    return 0;
  }
}

经验之谈:自动恢复一定要有「回退」机制。当网络恢复后,要能自动升回原来的质量等级。我见过一些系统,降级后就不升回去了,用户全程看低清视频,体验很差。

23.6 知识体系总览

为了让你更直观地理解整个监控体系的架构,我画了一张流程图:

WebRTC 监控与自动恢复体系 Stats API 采集 getStats() 每秒轮询 自定义指标 CPU/内存/网络类型 用户事件 点击/切换/异常 数据聚合与格式化 批量上报 / 本地缓存 / 时序对齐 告警引擎 瞬时告警 | 持续告警 | 趋势告警 自动恢复(降码率/切音频) 人工介入(告警通知/日志)

这张图展示了从数据采集到自动恢复的完整链路。你想想看,如果没有这套体系,出了问题你只能靠猜。有了它,你就能像医生看心电图一样,精准定位病灶。

23.7 写在最后

WebRTC的监控不是一蹴而就的。我建议你从最简单的Stats API采集开始,先把数据拿到手,再逐步加上告警和自动恢复。记住:先有数据,再有分析,最后才是自动化

如果你在实施过程中遇到什么坑,欢迎来找我聊聊。毕竟,这些经验都是我用一个个不眠之夜换来的。

本章核心要点:

  • Stats API是WebRTC监控的基础,必须定时采集
  • 关注RTT、丢包率、FPS三个核心指标
  • 自定义上报要批量、带时间戳、有重试机制
  • 告警分三层:瞬时、持续、趋势
  • 自动恢复要分级,且必须有回退机制

公众号:蓝海资料掘金营,微信deep3321