PAYATHON 2026

Google App Engine Channel API 如何计费?

技术老齐

结论

Channel API 不会按“创建 channel 的次数”或“发送消息的次数”单独收费。相关费用主要来自应用实例的 CPU 和运行时间、Datastore 等后端操作,以及网络流量。Channel API 页面中的 daily quota 和 Billing Default Quota 是容量限制,不是价格表,不能用“channel 数量 × 单价”来计算费用。

需要注意的是,Google App Engine Channel API 已经停止服务,不适合新项目使用。现在规划产品时,应考虑 WebSocket、Server-Sent Events,或 Firebase 等实时消息服务。以下关于 quota、token 和成本估算的内容,主要面向仍在维护 Channel API 的旧系统。

为什么找不到每个 channel 的价格

Channel API 的资源模型不同于 CPU、实例时间或出口流量:

  • 创建 channel token 没有单独的按次价格。
  • 通过 Channel API 发送消息没有公开的“每条消息单价”。
  • 创建 token、运行应用逻辑和处理请求会消耗应用实例资源。
  • 消息和接口请求可能产生网络流量。
  • 如果发送消息前需要查询 Datastore、Memcache 或其他服务,还要计入这些服务的用量。

因此,Channel API quota 说明“最多可以使用多少”,Billing 页面则说明“哪些基础资源会产生费用”。两者不能直接换算。

如何提前估算费用

估算时不能只统计 channel 数量,还要建立完整的请求模型。假设:

  • 每天有 (U) 个活跃用户;
  • 每个用户每天创建 (T) 个 token;
  • 每个用户每天接收 (M) 条消息;
  • 每条消息的平均大小为 (S) 字节;
  • 每次创建 token 或发送消息引发的应用请求平均执行时间为 (R);
  • 每条消息还需要 (D) 次 Datastore 操作。

可以先计算:

[ \text{Daily token creations} = U \times T ]

[ \text{Daily messages} = U \times M ]

[ \text{Message payload traffic} \approx U \times M \times S ]

[ \text{Daily Datastore operations} \approx U \times M \times D ]

实际网络流量通常高于消息正文大小,因为其中还包括 HTTP、协议封装、重试和其他元数据。实例成本也不能简单地按业务处理时间累加,它还会受到实例类型、自动扩缩容、并发配置和空闲实例行为的影响。

较可靠的评估方法是:

  1. 在测试环境中模拟预期的并发用户数和消息频率。
  2. 分别记录实例时间、请求数、出口流量和数据库操作数。
  3. 将测试结果换算成“每千名日活用户”的资源消耗。
  4. 根据当前采用的替代服务及 Google Cloud 资源价格计算预算。
  5. 为连接重建、重试、流量峰值和监控日志留出余量。

例如,压测结果显示,1,000 名日活用户每天消耗 2 GiB 出口流量和 3 小时实例时间,那么 50,000 名日活用户的粗略基线可以写成:

[ 50 \times (2\ \text{GiB} + 3\ \text{instance-hours}) ]

这只是线性估算。实时系统在高峰期可能因并发、实例扩容和连接重建而出现非线性增长。

一个便于统计用量的示例

旧版 Python Channel API 的典型逻辑如下:

from google.appengine.api import channel

def create_user_channel(user_id):
    token = channel.create_channel(user_id)
    return token

def notify_user(user_id, payload):
    channel.send_message(user_id, payload)

为了获得可用于预算的数据,可以在业务层记录 token 创建次数、消息数量和消息字节数,而不是只依赖管理控制台中的低流量统计:

import json
from google.appengine.api import channel

def notify_user(user_id, event):
    payload = json.dumps(event, separators=(",", ":"))

    metrics.increment("channel.messages")
    metrics.increment("channel.payload_bytes", len(payload.encode("utf-8")))

    channel.send_message(user_id, payload)

这里的 metrics 只是示意,实际使用时应替换为项目中的监控或日志系统。不要为每条消息写入成本较高的同步统计记录,否则统计操作本身可能成为新的成本和性能瓶颈。

quota 不够时怎么办

在 Channel API 仍受支持时,部分 quota 可以申请提高。是否批准、最高可以提高到多少,以及申请是否涵盖某项 Channel API 指标,都要由 Google 根据项目情况和当时的服务政策决定。不能只凭通用 quota 申请表,就认定一定可以扩容。

可以按以下方式处理:

  1. 在控制台确认具体是哪个指标接近上限。
  2. 准备日活用户数、峰值并发量、token 创建频率和消息速率等数据。
  3. 提前提交申请,不要等到正式发布后触及上限。
  4. 准备降级方案,不要把提高 quota 当作确定结果。

对于现在的新系统,不应再通过申请旧 Channel API quota 来解决容量问题,而应选择仍受支持、扩容方式明确的实时通信方案。

如何更高效地使用 channel

复用 token

不要为每条消息或每次页面操作重新创建 token。token 应绑定到稳定的客户端标识,并在有效期内复用。

token 不是永久凭证。客户端需要在 token 过期、连接断开或页面重新加载后重新获取 token。

使用稳定且唯一的 client ID

不能假设“一个用户永远只有一个连接”。同一账号可能同时打开多个标签页,也可能在多台设备上登录。连接标识可以设计为:

user_id + device_id + session_id

如果业务规定每个用户只能维持一个逻辑连接,就需要明确处理旧连接被替换的情况。

避免无意义的 token 重建

一种常见错误是,每次轮询、Ajax 请求或发送消息时都调用 create_channel()。token 应在建立客户端会话时创建,并通过缓存或会话存储管理。

合并高频更新

如果短时间内会出现大量状态变化,可以只发送最新状态,或者将多条小事件合并成一条消息:

{
  "type": "batch_update",
  "items": [
    {"id": "A17", "status": "ready"},
    {"id": "B04", "status": "failed"}
  ]
}

这样可以减少消息数量和客户端重绘次数。不过,批次不能过大,否则会增加延迟,以及失败后的重传成本。

消息只携带必要数据

实时通知可以只包含事件类型和资源 ID,再由客户端按需获取完整数据:

{
  "type": "order_updated",
  "order_id": "12345",
  "version": 18
}

但也不能机械地把所有数据拆成二次请求。消息过大和请求过多之间如何取舍,需要通过压测确定。

正确处理断线和重复消息

客户端应支持:

  • 断线后的退避重连;
  • token 失效后重新获取;
  • 检查消息序号或版本号;
  • 对重复消息进行幂等处理;
  • 长时间离线后的状态补偿。

不能把 Channel API 当作可靠的持久消息队列。重要事件应先写入持久存储,再通过实时通道通知客户端。客户端恢复连接后,还应能够重新读取缺失的状态。

新项目的替代方案

如果需要在浏览器与服务器之间进行实时双向通信,可以考虑:

  • 在支持 WebSocket 的运行环境中自行维护连接;
  • 使用 Firebase Realtime Database 或 Cloud Firestore 的实时监听能力;
  • 使用托管的实时消息服务;
  • 在单向服务器推送场景中使用 Server-Sent Events;
  • 使用 Pub/Sub 传递后端异步事件,但不要把 Pub/Sub 直接当作浏览器长连接服务。

不同方案可能按连接数、消息数、读取次数、存储量、实例时间或出口流量收费。选型时,应根据“峰值并发连接数、每秒消息数、平均消息大小、可靠性要求和数据持久化要求”重新建立成本模型,不要继续套用旧 Channel API 的 quota。

备注:内容仅供参考。