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 仍持续高出数倍,就应按平台统计或计费异常处理,并通过独立应用和可重复的冷缓存测试向平台方举证。
备注:内容仅供参考。
