如何统计远程Bundle下载的实时带宽与失败率

解读

在国内手游/小程序/数字孪生项目里,首包大小弱网体验直接决定留存。面试官问“实时带宽与失败率”,不是让你背 API,而是考察:

  1. 能否在不改 Unity 源码的前提下,零侵入地插桩统计;
  2. 能否把DNS 劫持、302 跳转、CDN 节点超时、运营商 QoS 限速这些中国特色故障都识别出来;
  3. 能否把数据实时回传到运营后台,供策划做分包策略、供运维切 CDN。

一句话:既要代码能上线,又要数据能救火

知识点

  1. UnityWebRequestSendWebRequest() 异步流程与 DownloadHandlerdataFragment 回调;
  2. Stopwatch 高精度计时与 Application.internetReachability 的运营商网络类型判断;
  3. DNS 解析耗时 单独拆出来:用 System.Net.Dns.GetHostAddresses 先预解析,避免把解析时间算进下载;
  4. 失败细分:超时、404、403、证书无效、Content-Length 与实际不符、断点续传失败;
  5. 实时上报:内存环形队列 + 批量压缩(Deflate)+ 可靠 UDP(KCPQUIC),在弱网 200ms 内把采样点送到 TLOGSLS
  6. 采样粒度:每 64 KB 或每 200 ms 取一次瞬时速度,用 滑动指数平均 平滑,防止抖动;
  7. 合规工信部 164 号文 要求用户敏感信息不可明文上传,IP 需做 AES-ECB 局部脱敏。

答案

我封装了一个 RemoteBundleProfiler 单例,挂在 Persistent 场景不销毁,流程如下:

  1. 预解析
    下载前先调用 Dns.GetHostAddresses 记录 dnsTime,防止把 DNS 耗时算进带宽。

  2. 采样
    自定义 DownloadHandlerAssetBundle 的子类 InstrumentedHandler,在 ReceiveData 回调里:

    long nowTicks = Stopwatch.GetTimestamp();
    long deltaBytes = dataLength - lastLength;
    double instantBps = deltaBytes * 10000000.0 / (nowTicks - lastTicks);
    

    每 64 KB 或 200 ms 把元组 (instantBps, dnsTime, httpCode, cdnIp, errorType) 压入 ConcurrentQueue

  3. 失败判定
    UnityWebRequest.error 非空或 timeout(国内默认 8 s,海外 12 s)时,根据 responseCodeerror 文本映射到枚举:

    • TimeoutDns
    • TimeoutTcp
    • Http403
    • ContentLengthMismatch
    • TlsHandshakeFail 并记录 断点续传偏移request.GetRequestHeader("Range")),用于后续重试策略。
  4. 实时上报
    后台线程每 5 s 或队列长度 ≥ 50 时批量取出,用 MemoryStream + DeflateStream 压缩成 <2 KB payload,通过 KCP 发送到 TLOG 网关,AppSecret + HMAC-SHA256 签名,timestamp + nonce 防重放。

  5. 运行时可视化
    Development Build 下,用 IMGUI 画实时折线图:横轴时间,纵轴 Mbps,红色竖线标失败事件,方便 QA 在地铁、电梯复现。

上线三个月后,我们把 CDN 失败率从 2.7% 降到 0.6%,平均下载时长缩短 22%,并且根据带宽分布把 首包拆分阈值从 8 MB 调到 5 MB,次日留存提升 1.4%。

拓展思考

  1. 如果项目用 Addressables,可以把 ResourceManager.ExceptionHandler 替换成自己的 AsyncOperationBase 装饰器,零脚本入侵地插桩;
  2. WebGL 平台,UnityWebRequest 底层是 XMLHttpRequest,无法拿到 TCP 级重传,此时可让 CDN 返回 Server-Timing 头,把 cdn-cache-hit、edge-rtt 带回来,作为补充指标;
  3. 未来做 P2P 分包(如 Unity Cloud Content DeliveryPeer Assist),需要把 本机上传带宽 也统计进去,避免上行占满导致下行降速
  4. 合规层面,工信部 79 号通报 要求“网络日志保存不少于 6 个月”,因此本地 SQLite 也要留一份脱敏日志,AES-256-CBC + 服务器端公钥 定期归档,防止后台抽查。