LabHub
学习 学习路径 课程

队列与异步 API

实现指数退避与 DLQ

在 LabHub 中继续学习

目标

实现重试的四个要素——指数退避、抖动、上限、放弃后的 DLQ——并仅凭 DLQ 中的信息查明原因,直至完成重新处理。

为什么重要

重试看似最简单,却是最常引发事故的代码。以固定间隔无限重试,会周期性地再次压垮正在恢复的服务器。只加入指数退避时,1 万个客户端仍会在同一时刻重试,形成冲击波。即使加入抖动,如果没有上限,第 10 次尝试也要等待 17 分钟。如果没有放弃条件,一条错误消息就会永远阻塞整个队列。这称为毒消息。DLQ 是隔离该消息、让其余消息继续流动的机制,同时也是待调查事项列表。因此,只把消息放进去毫无用处——必须同时记录原因、尝试次数和首次时间,才能修复原因并重新处理。

步骤

  1. 使用 /root/qr/worker.py 创建一个消费 q:tasks 的工作进程。/opt/app/taskproc.py 中的 process(msg)msg["kind"]bad 时抛出异常。
  2. 将失败消息的尝试次数加 1 后重新放入队列。/root/qr/requeue.out 中必须显示 attempts=2
  3. /root/qr/schedule.txt 中,以 attempt=<n> wait=<초> 格式写入 base 为 1 秒、尝试编号 0~5 的指数退避值,共 6 行。其值为 1、2、4、8、16、32。
  4. /root/qr/jitter.txt 中逐行写入 20 个尝试编号 3 的完全抖动等待时间。所有值必须在 0 到 8 之间,并且至少有 15 个不同值。
  5. 应用 30 秒的上限,在 /root/qr/capped.txt 中写入尝试编号 0~8 的值。最后三个值必须都为 30。
  6. 将失败 5 次的消息发送到 q:tasks:dlqq:tasks 必须为空,DLQ 长度必须为 1。
  7. DLQ 消息 JSON 必须包含 payloaderrorattemptsfirst_seen_at 四个键,且 attempts 为 5。
  8. /root/qr/replay.py 从 DLQ 取出消息,将 attempts 重置为 0,再放入 q:tasks。执行后 DLQ 必须为空,q:tasks 中必须有 1 条消息。

参考

创建队列消费工作进程

使用 /root/qr/worker.py 创建一个消费 q:tasks 的工作进程。/opt/app/taskproc.py 中的 process(msg)msg["kind"]bad 时抛出异常。

逐条取出消息并交给处理函数。请让处理函数能够故意失败。

将失败消息重新入队

将失败消息的尝试次数加 1 后重新放入队列。/root/qr/requeue.out 中必须显示 attempts=2

将尝试次数放在消息中,下次消费时就可以接着计数。

计算指数退避计划

/root/qr/schedule.txt 中,以 base 1 秒、尝试编号 0~5 的指数退避值 attempt=<n> wait=<초> 格式写入 6 行。其值为 1、2、4、8、16、32。

以尝试编号为指数。先完成计算并保存到文件中确认,然后再放入代码。

应用完全抖动

/root/qr/jitter.txt 中逐行写入 20 个尝试编号 3 的完全抖动等待时间。所有值必须在 0 到 8 之间,并且至少有 15 个不同值。

它是从 0 到计算值之间的随机数。即使尝试编号相同,每次的值也必须不同。

设置等待时间上限

应用 30 秒的上限,在 /root/qr/capped.txt 中写入尝试编号 0~8 的值。最后三个值必须都为 30。

指数增长得很快。如果没有上限,第 10 次尝试要等待 17 分钟。

失败 5 次后发送到 DLQ

将失败 5 次的消息发送到 q:tasks:dlqq:tasks 必须为空,DLQ 长度必须为 1。

有放弃条件才称得上重试。消息必须从原队列消失,并且只存在于 DLQ 中。

在 DLQ 消息中加入调查信息

DLQ 消息 JSON 必须包含 payloaderrorattemptsfirst_seen_at 四个键,且 attempts 为 5。

必须有原始消息、失败原因、尝试次数和首次接收时间这四项,之后才能开展调查。

消除原因后重新处理

/root/qr/replay.py 从 DLQ 取出消息,将 attempts 重置为 0,再放入 q:tasks。执行后 DLQ 必须为空,q:tasks 中必须有 1 条消息。

从 DLQ 取出消息放回原队列,并重置尝试次数。必须由明确的操作来执行,而不是自动循环。