管理扩缩风险

Google 的基础架构旨在通过高度的扩缩能力实现弹性运营,让大多数层可以适应大规模增加的流量需求。实现这一理念的核心设计模式是自适应层,一种基于流量模式动态重新分配负载的基础架构组件。然而,这种适应需要时间。由于 Cloud Tasks 可以调度非常大量的流量,因此在流量攀升速度快于基础架构的适应能力的情况下,它会对生产造成风险。

概览

本文档提供了有关在高流量队列中保持高 Cloud Tasks 性能的最佳做法的指南。高 TPS 队列是每秒 (TPS) 创建或分派 500 个任务或更多任务的队列。高 TPS 队列组是总共创建或分派了至少 2000 个任务的一系列连续的队列,例如 [queue0001queue0002queue0099]。如需查看队列或队列组的历史 TPS,您可以使用 Stackdriver 指标、“CreateTask”操作次数的 api/request_count 以及任务尝试次数的queue/task_attempt_count。高流量队列和队列组容易出现两种不同类型的故障:

当任务创建和分派到单个队列或队列组的速度快于队列基础架构能够适应的速度时,就会发生队列过载。同样,当调度任务的速率导致下游目标基础架构中的流量达到峰值时,就会发生目标过载。在这两种情况下,我们建议遵循 500/50/5 模式,即当规模超过 500 TPS 时,每 5 分钟增加的流量不超过 50%。本文档分析了可能引入扩缩风险的不同场景,并提供了如何应用此模式的示例。

队列过载

流量突然增加时,队列或队列组可能会过载。因此,这些队列会发生如下情况:

  • 任务创建延迟增加
  • 任务创建错误率增加
  • 调度率降低

为了防范这种状况,我们建议在会导致队列或队列组的创建或调度速率突然上升的任何情况下都建立控制。 我们建议每秒最多操作冷队列或队列组 500 次,然后每 5 分钟增加 50% 的流量。从理论上讲,使用这种渐增方案,90 分钟后可增加到每秒 74 万次操作。 这种状况可能在多种情况下发生。

例如:

  • 启动大量使用 Cloud Tasks 的新功能
  • 在队列之间移动流量
  • 在更多或更少的队列中重新平衡流量
  • 运行注入大量任务的批处理作业

在这些状况和其他情况下,请遵循 500/50/5 模式。

使用 App Engine 流量拆分

如果任务是由 App Engine 应用创建的,您可以利用 App Engine 流量拆分(标准/柔性)来顺利应对流量增加的情况。通过在多个版本(标准/柔性)之间拆分流量,需要进行速率管理的请求的处理速度可以逐渐加快,从而确保队列运行状况良好。例如,考虑将流量调整到新扩展的队列组的情况:让 [queue0000queue0199] 成为一系列高 TPS 队列,在峰值时总共接收 100000 TPS 创建。

[queue0200,queue0399] 设置为一系列新队列。在所有流量迁移之后,序列中的队列数量增加了一倍,新队列范围接收到的流量为序列总流量的 50%。

部署增加队列数量的版本时,使用流量拆分将流量逐渐增加到新版本,从而增加到新队列:

  • 开始将 1% 的流量迁移到新版本。例如,100000 TPS 的 1% 的 50% 将使 500 TPS 迁移到新队列的集合。
  • 每隔 5 分钟,将发送到新版本的流量增加 50%,如下表所示:
自部署起的分钟数 总流量转移到新版本的百分比 总流量占新队列的百分比 总流量占旧版本的百分比
0 1.0 0.5 99.5
5 1.5 0.75 99.25
10 2.3 1.15 98.85
15 3.4 1.7 98.3
20 5.1 2.55 97.45
25 7.6 3.8 96.2
30 11.4 5.7 94.3
35 17.1 8.55 91.45
40 25.6 12.8 87.2
45 38.4 19.2 80.8
50 57.7 28.85 71.15
55 86.5 43.25 56.75
60 100 50 50

版本驱动的流量急剧增加

在发布显着增加队列或队列组流量的版本时,逐步发布也是平滑流量增加的重要机制。逐步发布您的实例,使初始发布对新队列的总操作不超过 500 次,每 5 分钟增加不超过 50% 的流量。

新的高 TPS 队列或队列组

新创建的队列特别容易受到影响。队列组(例如 [queue0000、queue0001、…、queue0199])在初始发布阶段与单个队列一样敏感。对于这些队列,逐步发布是一项重要的策略。发布新的或更新的服务,这些服务分阶段创建高 TPS 队列或队列组,使初始负载低于 500 TPS,每增加 50% 或更少流量需要 5 分钟或更长时间。

新扩展的队列组

增加队列组的总容量时,例如将 [queue0000-queue0199] 扩展到 [queue0000-queue0399] 时,请遵循 500/50/5 模式。值得注意的是,对于发布过程,新队列组的行为与单个队列没有区别。将 500/50/5 模式作为一个整体应用于新组,而不仅仅是组中的各个队列。对于这些队列组的扩展,逐步发布也是一个重要的策略。如果您的流量来源是 App Engine,则可以使用流量拆分(请参阅版本驱动的流量急剧增加)。当您迁移服务以将任务添加到增加的队列时,逐步发布实例,使初始发布对新队列执行的总操作不超过 500 次,每 5 分钟增加不超过 50% 的流量。

紧急队列组扩展

有时,您可能希望扩展现有队列组,如预计将任务添加到队列组的速度快于队列组可以调用它们的速度时。在按字典顺序排序时,如果新队列的名称均匀分布在现有队列名称中,则只要交替插入的新队列不超过 50% 并且每个队列的流量少于 500 TPS,流量就会立即发送到这些队列。此方法是如上所述的流量拆分逐步发布方法的替代方案。

可以通过将后缀附加到以偶数结尾的队列来命名这种交错类型的队列。例如,如果您现在有 200 个队列 [queue0000 - queue0199],并且想要创建 100 个新队列,请选择 [queue0000a、queue0002a、queue0004a、…、queue0198a] 作为新队列名称,而不是 [queue0200 - queue0299]。

如果需要进一步增加队列,您仍然可以每 5 分钟交错最多 50% 的队列。

大规模/批量任务加入队列

当需要添加大量任务(例如数百万或数十亿)时,请考虑使用双注入模式。不使用单个作业创建任务,而是使用注入器队列。添加到注入器队列的每个任务都会扇出,并将 100 个任务添加到所需的队列或队列组。注入器队列可以随时间加速,例如从 5 TPS 开始,每 5 分钟增加 50%。

任务命名

创建新任务时,默认情况下,Cloud Tasks 会为任务分配唯一的名称。不过,您可以使用 name 参数自行为任务命名。但是,这会带来显著的性能开销,从而导致延迟增加,并可能升高与命名任务相关的错误率。如果按顺序命名任务(例如使用时间戳),则这些成本还会大幅提升。因此,如果您自行命名任务,我们建议您为任务名称使用分布合理的前缀,例如内容的哈希值。如需了解任务命名的详情,请参阅文档

目标过载

如果队列中的调度在短时间内急剧增加,则 Cloud Tasks 可能会使您正在使用的其他服务过载,如 App Engine、Datastore 和网络使用。如果积压了过多任务,那么取消暂停这些队列可能会使这些服务过载。对此,我们建议采用与队列过载相同的 500/50/5 模式,即如果队列调度超过 500 TPS,则每 5 分钟使队列触发的流量增加不超过 50%。另外,请使用 Stackdriver 指标主动监控您的流量增长。其中的 Stackdriver 警报可检测潜在的危险情况。

取消暂停或恢复高 TPS 队列

当某个队列或一系列队列被取消暂停或重新启用时,队列将恢复调度。如果队列中有许多任务,则新启用的队列的调度速率可能会从 0 TPS 急剧增加到队列的全部容量。为了加快速度,请错时恢复队列或使用 Cloud Tasks 的 maxDispatchesPerSecond 控制队列调度速率。

批量计划任务

计划同时调度的大量任务也会带来目标过载的风险。如果需要同时启动大量任务,请考虑使用队列速率控制来逐步提高调度速率或提前明确调整目标容量。

扇出增加

更新通过 Cloud Tasks 执行的服务时,增加远程调用的数量可能会产生生产风险。例如,高 TPS 队列中的任务会调用处理程序 /task-foo。又或者,如果新版本向处理程序添加了几个昂贵的 Datastore 调用,则新版本可能会大幅提升调用 /task-foo 的费用。此类版本的最终结果是导致 Datastore 流量的大量增加,这与用户流量的变化直接相关。请使用逐步发布或流量拆分来管理加速。

重试

在进行 Cloud Tasks API 调用时,您的代码可以在失败时进行重试。但是,当很大一部分请求因服务器端错误而失败时,较高的重试率会使更多的队列过载,并导致它们恢复得更慢。因此,如果您的客户端检测到很大一部分请求因服务器端错误而失败,我们建议限制传出流量,例如使用网站可靠性工程手册“过载处理”一章中描述的自适应限制算法。Google 的 gRPC 客户端库实现了此算法的变体