PAYATHON 2026

GAE Outgoing bandwidth 无故增至4倍,如何排查?

数据小许

结论

这次异常并不是业务请求量突然增长,也不像是 urlFetch、Blobstore、Mail 或 Channel API 引起的流量泄漏。对照测试表明,问题来自 GAE 对静态资源 Outgoing bandwidth 的统计或计费异常:

  • DAU 和应用日志中的动态响应量基本稳定;
  • 在新的 app-id 上,浏览器实际下载约 65MB,Dashboard 却记录了约 0.33GB;
  • 静态资源请求在日志中显示为 200 0,无法通过汇总 response size 完成对账;
  • 二次访问命中浏览器缓存后,客户端流量和 Dashboard 计数都没有明显增加;
  • 平台最终修复了这一异常。

因此,日志合计约 4GB,Dashboard 却显示 40GB+,实际包含两个问题:访问日志没有完整记录静态文件的传输量,而 Dashboard 当时又高估了实际出站流量。

为什么日志和 Dashboard 对不上

GAE 的请求日志不能视为完整的网络流量账本。由静态文件处理器直接提供的资源,可能留下这样的记录:

GET /game/3.cache.js HTTP/1.1" 200 0

这里的 0 并不代表没有传输响应体。浏览器可能实际下载了数 MB 的 JavaScript 或图片,只是这些字节数没有写入普通应用请求日志。

因此:

sum(log.response_size) != Dashboard Outgoing bandwidth

即使系统运行正常,两者也可能存在差异,常见原因包括:

  • 静态文件由平台前端直接提供,没有进入应用实例;
  • 日志中的响应大小不包含部分协议开销;
  • Blobstore 等托管服务可能使用不同的日志路径;
  • 日志导出不完整、写入延迟,或者统计周期边界不同;
  • 压缩前大小、压缩后大小和计费口径可能不同。

不过,这些因素通常不足以解释稳定出现的 4~5 倍差距。复现实验中,客户端实际传输约 65MB,Dashboard 却记录了约 330MB,异常已经十分明显。

建议的排查步骤

1. 先区分动态响应与静态资源

按照 URL 路径分别统计请求次数和响应大小,不要只汇总全部日志。例如,可以分成:

/rpc/*
/api/*
/game/*.cache.js
/images/*
/audio/*
/download/*

如果动态接口的请求量和响应量保持稳定,而 Outgoing bandwidth 突然增加,应优先检查静态资源、托管下载和平台计量。

2. 用浏览器测量真实传输量

在无痕窗口中打开页面,或者先清空缓存,再通过浏览器 DevTools 的 Network 面板记录:

  • Transferred;
  • Resource size;
  • HTTP 状态码;
  • Content-Encoding;
  • Cache-Control;
  • 资源是否来自 memory cache、disk cache 或 Service Worker。

不要把资源解压后的大小当成网络传输量。排查带宽时,应以 Transferred 为主。

也可以用 curl 辅助验证单个资源:

curl -sS \
  -H 'Accept-Encoding: gzip' \
  -D headers.txt \
  -o resource.bin \
  'https://example.appspot.com/game/3.cache.js'

wc -c resource.bin

检查响应头时,可以重点关注:

Content-Length
Content-Encoding
Cache-Control
ETag
Last-Modified
Age
Via

curl 下载得到的是实际响应体大小,不包括 TLS、TCP 等协议开销,因此不可能与计费数据逐字节一致。

3. 创建最小化复现环境

把同一版本部署到独立的 app-id,可以有效排除原应用中其他流量的干扰。测试时应记录:

测试开始时间
测试结束时间
浏览器数量
是否清空缓存
请求次数
客户端 Transferred 合计
日志 response_size 合计
Dashboard Outgoing bandwidth 增量

然后计算放大倍数:

放大倍数 = Dashboard 带宽增量 / 客户端实际传输量

例如:

Dashboard: 0.33GB
客户端合计: 65MB
放大倍数约为: 0.33 × 1024 / 65 ≈ 5.2

如果多轮冷缓存测试都得到相近的异常倍数,而热缓存测试又不会增加带宽,基本可以把问题锁定在静态资源传输的计量链路,而不是用户行为或后台任务。

4. 排除不经过普通响应日志的流量

还需要逐项检查:

  • Blobstore 或文件下载;
  • Cloud Storage 下载;
  • urlFetch;
  • Mail API;
  • Channel API;
  • Task Queue 和 Cron 触发的外部请求;
  • 图片处理或媒体服务;
  • 管理接口、导出接口和爬虫访问;
  • 异常客户端反复绕过缓存;
  • 持续变化的静态资源 URL 或查询参数。

尤其要检查是否生成了这样的 URL:

/game/3.cache.js?v=随机值
/images/map.png?t=当前时间

即使文件内容相同,URL 不断变化也可能导致浏览器和 CDN 无法复用缓存。

5. 向平台支持提交可复现证据

只说明“流量变高了”,通常很难帮助平台定位问题。提交以下信息会更有效:

  • 受影响的 app-id;
  • 精确到时区的测试时间段;
  • 测试资源 URL;
  • 客户端实际传输量;
  • Dashboard 增量;
  • 对应的请求日志;
  • 是否清空缓存;
  • 是否启用 gzip;
  • 能否在第二个 app-id 上复现;
  • 多次测试得到的固定放大倍数。

这些材料可以证明异常发生在平台计量侧,而不是应用代码内部。

静态资源缓存配置

即使平台统计已经恢复正常,大型 GWT 应用仍应为带内容哈希的资源配置长期缓存。下面的示例只用于说明思路,具体语法需要根据所使用的 GAE runtime 调整:

handlers:
  - url: /game
    static_dir: game
    secure: always
    expiration: "365d"

  - url: /images
    static_dir: images
    secure: always
    expiration: "30d"

  - url: /.*
    script: auto
    secure: always

长期缓存只适用于文件名会随内容变化的资源,例如:

3A51F074C2B8.cache.js
sprite.9f42c1.png

如果固定 URL 下的内容会被覆盖更新,就不应直接设置一年缓存,否则用户可能长期拿到旧版本。

理想的响应头类似:

Cache-Control: public, max-age=31536000, immutable

入口 HTML 或 GWT 的选择脚本通常需要较短的缓存时间,以便及时引用新的哈希资源:

Cache-Control: no-cache

是否需要使用 CDN

对于包含大量图片、JavaScript 和音频的游戏,CDN 可以减少源站的静态流量和请求数。Cloudflare 或其他 CDN 都能承担这一角色,但它们只能优化后续流量,无法解释或修正已经发生的平台计量异常。

接入 CDN 后应确认:

  • 静态资源确实产生了 CDN cache hit;
  • CDN 没有缓存用户私有接口;
  • HTML 和哈希静态资源使用不同的缓存策略;
  • HTTPS、压缩和跨域资源配置正常;
  • URL 查询参数没有意外破坏缓存;
  • CDN、浏览器和 GAE 三方统计能够相互对照。

注意事项

不要把日志中的 response_size = 0 理解为“没有产生带宽”。在这个案例中,它只表示静态资源的字节数没有正确显示在普通请求日志中。

也不能只根据 DAU 判断带宽是否合理。新用户比例、缓存失效、资源版本变化和爬虫访问,都可能明显改变单个用户产生的流量。不过,如果用户量、部署版本和客户端实测结果都很稳定,Dashboard 仍持续高出数倍,就应按平台统计或计费异常处理,并通过独立应用和可重复的冷缓存测试向平台方举证。

备注:内容仅供参考。