Redis Queue / BullMQ / Celery / Temporal / Inngest — 当「请求-响应」不够用时,如何构建可靠的异步处理系统
HTTP 请求-响应模型有一个硬限制:用户不想等。当一个操作需要几秒甚至几分钟——发邮件、生成报表、视频转码、AI 推理——同步处理会让请求超时、连接断开、用户体验灾难。
典型的异步场景:
在深入各个工具之前,先理解异步任务系统的通用概念——这些概念在不同工具中以不同名字出现,但本质相同。
这是最基本的模型。Producer(生产者)将任务描述序列化后放入 Queue(队列),Consumer(消费者/Worker)从队列取出任务执行。生产者和消费者解耦,可以独立扩展。
| 术语 | 含义 | 不同工具的叫法 |
|---|---|---|
| Job / Task | 一个待执行的工作单元,包含类型和参数 | Celery: Task / BullMQ: Job / Temporal: Workflow / Inngest: Function |
| Queue | 存储待执行 Job 的有序容器 | Celery: Queue / BullMQ: Queue / Temporal: Task Queue / Inngest: Event |
| Worker | 从队列取 Job 并执行的进程 | Celery: Worker / BullMQ: Worker / Temporal: Worker / Inngest: SDK Function |
| Retry | 任务失败后自动重新执行 | 所有工具都支持,但策略不同 |
| DLQ (Dead Letter Queue) | 重试耗尽后的"坟墓"——不可恢复的失败任务 | BullMQ: Failed Queue / Celery: Dead Letter / Inngest: Cancelled |
| Priority | 任务的执行优先级——VIP 先处理 | BullMQ / Celery 支持 / Temporal 通过多队列实现 |
| Cron / Scheduled | 定时触发——每天 3 点、每 5 分钟 | Celery Beat / BullMQ Repeatable / Temporal Schedule / Inngest Cron |
| Ack / Nack | 确认任务完成 / 标记任务失败 | 源自 AMQP 协议,所有系统都有类似机制 |
层次 1:At-Most-Once(最多一次)
任务可能丢失,但不会重复。适用于「丢了也无妨」的场景(如日志处理)。实现最简单——fire and forget。
层次 2:At-Least-Once(至少一次)
任务不会丢失,但可能重复执行。绝大多数队列系统默认提供。需要消费者端幂等。
层次 3:Exactly-Once(恰好一次)
任务既不丢失也不重复。理论上很难(FLP 不可能定理),实践中通过幂等 + 去重 + 事务近似实现。Temporal 的 Workflow 在这方面做得最好。
Python 轻量 Redis
RQ (Redis Queue) 是 Python 生态最简单的任务队列——直接用 Redis 的 LPUSH/BRPOP 实现队列,没有额外依赖,5 分钟上手。
RQ 是一个极简的 Python 任务队列库,将函数调用序列化为 Job 存入 Redis,Worker 进程从 Redis 取出执行。它的哲学是:简单到不需要学习。
lpush 入队,一个 brpop 出队rq worker 启动,fork 执行任务# tasks.py
import time
import requests
def send_welcome_email(user_id: int, email: str):
"""发送欢迎邮件——一个普通的 Python 函数"""
time.sleep(2) # 模拟 SMTP 调用
print(f"Welcome email sent to {email}")
return {"status": "sent", "user_id": user_id}
def generate_report(user_id: int, report_type: str):
"""生成报表——CPU 密集任务"""
time.sleep(10) # 模拟计算
report_url = f"https://cdn.example.com/reports/{user_id}-{report_type}.pdf"
return {"url": report_url, "type": report_type}
def process_image(image_url: str, operations: list):
"""图片处理管道"""
# 下载 → 处理 → 上传
img = requests.get(image_url).content
for op in operations:
if op == "resize":
img = resize_image(img, 800, 600)
elif op == "watermark":
img = add_watermark(img, "My SaaS")
return {"processed": True, "original_url": image_url}
# app.py — Flask 应用中入队
from redis import Redis
from rq import Queue
from tasks import send_welcome_email, generate_report
redis_conn = Redis(host="localhost", port=6379)
q = Queue("emails", connection=redis_conn) # 高优先级邮件队列
q_low = Queue("reports", connection=redis_conn) # 低优先级报表队列
# 基本入队
job = q.enqueue(send_welcome_email, 42, "user@example.com")
print(job.id) # 'a1b2c3d4-...'
# 入队时指定参数
job = q.enqueue(
generate_report,
42,
"monthly",
job_timeout="10m", # 任务超时
result_ttl=3600, # 结果保留 1 小时
failure_ttl=86400, # 失败记录保留 1 天
)
# 延迟执行
from datetime import datetime, timedelta
job = q.enqueue_in(timedelta(hours=1), send_welcome_email, 42, "user@example.com")
# 查询结果
job = q.enqueue(send_welcome_email, 42, "user@example.com")
# ... 稍后 ...
job.refresh() # 刷新状态
if job.is_finished:
print(job.result) # {'status': 'sent', 'user_id': 42}
elif job.is_failed:
print(job.exc_info) # 异常信息
# 命令行启动 Worker
$ rq worker emails reports # 监听两个队列
# 或在代码中启动
from rq import Worker
worker = Worker([q, q_low], connection=redis_conn)
worker.work()
# 带参数启动
$ rq worker emails \
--url redis://localhost:6379 \
--burst # 处理完所有任务后退出(适合批处理)
Node.js Redis 功能丰富
BullMQ 是 Node.js 生态中最强大的 Redis 队列库。它基于 Bull(v3)重写,支持优先级、延迟、限流、父子任务、进度追踪等高级特性,是 NestJS、Vercel 等项目的底层选择。
BullMQ 是一个基于 Redis 的高性能任务队列,专为 Node.js/TypeScript 设计。它将 Redis 用到极致——不是简单的 LPUSH/BRPOP,而是利用 Redis 的 Hash、Sorted Set、List、Stream 等多种数据结构实现优先级队列、延迟队列、限流、事件通知等功能。
// queue.ts
import { Queue, Worker, Job } from 'bullmq';
// 定义队列
const emailQueue = new Queue('emails', {
connection: { host: 'localhost', port: 6379 },
});
// 入队
await emailQueue.add('welcome', {
userId: 42,
email: 'user@example.com',
template: 'welcome',
}, {
attempts: 3, // 最多重试 3 次
backoff: { // 指数退避
type: 'exponential',
delay: 1000, // 1s, 2s, 4s...
},
removeOnComplete: { count: 1000 }, // 只保留最近 1000 个完成记录
removeOnFail: { age: 7 * 24 * 3600 }, // 7 天后删除失败记录
});
// 延迟任务
await emailQueue.add('trial-ending', data, {
delay: 3 * 24 * 3600 * 1000, // 3 天后执行
});
// 优先级任务
await emailQueue.add('vip-notification', data, {
priority: 1, // 数字越小优先级越高
});
// 定时任务 (Cron)
await emailQueue.add('daily-digest', data, {
repeat: {
pattern: '0 9 * * *', // 每天早上 9 点
tz: 'Asia/Shanghai',
},
});
// Worker
const worker = new Worker('emails', async (job: Job) => {
const { userId, email, template } = job.data;
// 更新进度
await job.updateProgress(10);
const user = await fetchUser(userId);
await job.updateProgress(50);
await sendEmail(email, template, user);
await job.updateProgress(100);
return { sent: true, email };
}, {
connection: { host: 'localhost', port: 6379' },
concurrency: 5, // 并发处理 5 个任务
limiter: { // 限流:每秒最多 10 个
max: 10,
duration: 1000,
},
});
// 事件监听
worker.on('completed', (job, result) => {
console.log(`Job ${job.id} completed:`, result);
});
worker.on('failed', (job, err) => {
console.error(`Job ${job?.id} failed:`, err.message);
});
worker.on('progress', (job, progress) => {
console.log(`Job ${job.id} progress: ${progress}%`);
});
// 用 Flow 编排多步骤任务
import { FlowProducer } from 'bullmq';
const flowProducer = new FlowProducer({
connection: { host: 'localhost', port: 6379 },
});
// 定义工作流:处理订单 → 生成发票 → 发送邮件
const flow = await flowProducer.add({
name: 'order-confirmation',
queueName: 'emails',
data: { template: 'order-confirmed' },
children: [
{
name: 'generate-invoice',
queueName: 'billing',
data: { orderId: 'ORD-123' },
children: [
{
name: 'process-payment',
queueName: 'payments',
data: { orderId: 'ORD-123', amount: 99.99 },
},
],
},
],
});
// 执行顺序:process-payment → generate-invoice → order-confirmation
// 父任务等所有子任务完成后才执行
// server.ts
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ExpressAdapter } from '@bull-board/express';
const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath('/admin/queues');
createBullBoard({
queues: [
new BullMQAdapter(emailQueue),
new BullMQAdapter(billingQueue),
],
serverAdapter,
});
app.use('/admin/queues', serverAdapter.getRouter());
// 访问 /admin/queues 即可查看任务状态、重试、删除
fastq / bee-queue(但功能远不如 BullMQ)Python 重量级 企业级
Celery 是 Python 生态中最老牌、最成熟的分布式任务队列。它支持多种 Broker(Redis、RabbitMQ、SQS)和多种 Result Backend,是 Django 项目的默认选择。
Celery 是一个分布式任务队列系统,核心由三部分组成:
与 RQ 不同,Celery 提供了完整的任务编排能力:Chain(链式)、Group(并行)、Chord(并行+回调)、Chunk(分块)。
# celery_config.py
broker_url = "redis://localhost:6379/0"
result_backend = "redis://localhost:6379/1"
# 序列化
task_serializer = "json"
result_serializer = "json"
accept_content = ["json"]
# 并发
worker_concurrency = 4 # Worker 进程数
worker_prefetch_multiplier = 4 # 每个 Worker 预取的任务数
# 任务超时
task_soft_time_limit = 300 # 软超时 5 分钟(抛异常)
task_time_limit = 600 # 硬超时 10 分钟(SIGKILL)
# 重试
task_acks_late = True # 任务完成后才确认(避免 Worker 崩溃丢任务)
task_reject_on_worker_lost = True # Worker 崩溃时重新入队
# 结果
result_expires = 3600 # 结果 1 小时后过期
# Beat 定时
beat_schedule = {
"cleanup-expired-sessions": {
"task": "app.tasks.cleanup_expired_sessions",
"schedule": crontab(hour=3, minute=0), # 每天凌晨 3 点
},
"send-daily-digest": {
"task": "app.tasks.send_daily_digest",
"schedule": crontab(hour=9, minute=0, day_of_week="mon-fri"),
},
}
# tasks.py
from celery import shared_task, chain, group, chord
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_email(self, to: str, subject: str, body: str):
"""发送邮件,失败自动重试"""
try:
smtp_send(to, subject, body)
except SMTPException as exc:
logger.warning(f"Email to {to} failed, retrying: {exc}")
raise self.retry(exc=exc) # 触发重试
@shared_task
def process_order(order_id: str):
"""处理订单"""
order = Order.objects.get(id=order_id)
# 扣库存、发通知、更新状态
order.status = "processed"
order.save()
return {"order_id": order_id, "status": "processed"}
@shared_task
def generate_invoice(order_id: str):
"""生成发票"""
invoice = Invoice.create(order_id=order_id)
return {"invoice_id": invoice.id, "url": invoice.pdf_url}
@shared_task
def notify_customer(order_id: str, invoice_url: str):
"""通知客户"""
order = Order.objects.get(id=order_id)
send_email.delay(order.email, "Your Invoice", f"View: {invoice_url}")
return {"notified": True}
# Chain:顺序执行,前一步结果传给下一步
workflow = chain(
process_order.s("ORD-123"), # 返回 {order_id, status}
generate_invoice.s(), # 接收 order_id
notify_customer.s(), # 接收 invoice_url
)
result = workflow.apply_async()
# Group:并行执行
from celery import group
batch = group(
send_email.s("a@example.com", "Hi A", "..."),
send_email.s("b@example.com", "Hi B", "..."),
send_email.s("c@example.com", "Hi C", "..."),
)
result = batch.apply_async()
result.get() # [True, True, True]
# Chord:并行执行 + 回调
# 先并行处理所有订单,全部完成后发送汇总
chord(
[process_order.s(o.id) for o in orders],
send_summary_email.s(admin_email) # 回调收到所有结果
).apply_async()
# Chunk:分块并行
# 1000 个邮件,每块 50 个
process_emails.chunks(
email_list, 50
).apply_async()
# proj/celery.py
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proj.settings")
app = Celery("proj")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks() # 自动发现各 app 的 tasks.py
# proj/__init__.py
from .celery import app as celery_app
__all__ = ("celery_app",)
# myapp/tasks.py — 在 Django app 中定义任务
from celery import shared_task
@shared_task
def my_task():
...
Durable Execution 企业级 多语言
Temporal 不是传统的任务队列——它是持久化执行引擎(Durable Execution Engine)。代码写的像同步,执行的像异步,状态自动持久化,进程崩溃后自动恢复。这是处理复杂业务流程的终极武器。
Temporal 的核心思想:代码即 Workflow。你写普通的同步代码,Temporal 保证它最终一定会执行完成——即使进程崩溃、服务器宕机、网络分区。
| 概念 | 说明 |
|---|---|
| Workflow | 一个持久化的执行流程。代码写的像普通函数,但每一步的状态都自动持久化。崩溃后从上次位置恢复。 |
| Activity | Workflow 中调用的具体操作(发邮件、调 API、操作 DB)。可重试、可超时、可取消。 |
| Signal | 外部向运行中的 Workflow 发送异步消息。例如用户取消订单 → Workflow 收到 Signal → 逻辑分支变化。 |
| Query | 外部向运行中的 Workflow 查询状态。不产生副作用,可以随时调用。 |
| Timer | 持久化定时器。即使进程重启,Timer 也会在正确的时间触发。 |
| Saga Pattern | 补偿事务模式。Workflow 中的每一步失败时,自动执行之前步骤的补偿操作。 |
// workflows.ts
import { proxyActivities, setHandler, condition, sleep, workflowInfo }
from '@temporalio/workflow';
import type * as activities from './activities';
// Activity 代理——实际执行的操作
const { chargeCreditCard, sendConfirmationEmail, refundCreditCard,
reserveInventory, releaseInventory, scheduleDelivery } =
proxyActivities({
startToCloseTimeout: '30s',
retry: { maximumAttempts: 3 },
});
// 订单处理 Workflow
export async function processOrder(order: Order): Promise {
let paymentConfirmed = false;
let orderCancelled = false;
// 监听取消 Signal
setHandler(cancelOrderSignal, () => { orderCancelled = true; });
try {
// Step 1: 预留库存
await reserveInventory(order.items);
// Step 2: 等待支付确认(最多 30 分钟)
const paymentReceived = await condition(
() => paymentConfirmed,
'30 minutes'
);
if (!paymentReceived || orderCancelled) {
// 超时或取消 → 释放库存
await releaseInventory(order.items);
return 'CANCELLED';
}
// Step 3: 扣款
await chargeCreditCard(order.paymentMethod, order.total);
// Step 4: 安排发货
const trackingId = await scheduleDelivery(order.shippingAddress);
// Step 5: 发送确认邮件
await sendConfirmationEmail(order.email, order.id, trackingId);
return trackingId;
} catch (error) {
// 补偿事务:释放库存 + 退款
await releaseInventory(order.items);
if (paymentConfirmed) {
await refundCreditCard(order.paymentMethod, order.total);
}
throw error;
}
}
// activities.ts
import { Context } from '@temporalio/activity';
export async function chargeCreditCard(
paymentMethod: string, amount: number
): Promise {
// Activity 可以有副作用——调 API、操作 DB
const heartbeat = Context.current().heartbeat;
heartbeat('charging');
const result = await stripe.charges.create({
amount: Math.round(amount * 100),
currency: 'usd',
source: paymentMethod,
});
return result.id; // charge_id
}
export async function sendConfirmationEmail(
email: string, orderId: string, trackingId: string
): Promise {
await mailer.send({
to: email,
subject: `Order ${orderId} Confirmed`,
body: `Tracking: ${trackingId}`,
});
}
// ... reserveInventory, releaseInventory, refundCreditCard, scheduleDelivery
# workflows.py
from datetime import timedelta
from temporalio import workflow
from temporalio.common import RetryPolicy
with workflow.unsafe.imports_passed_through():
from activities import send_email, process_data, save_result
@workflow.defn
class DataProcessingWorkflow:
"""数据处理 Workflow:处理 → 保存 → 通知"""
@workflow.run
async def run(self, data_id: str) -> str:
retry = RetryPolicy(
maximum_attempts=3,
initial_interval=timedelta(seconds=1),
backoff_coefficient=2.0,
)
# 处理数据
result = await workflow.execute_activity(
process_data,
data_id,
start_to_close_timeout=timedelta(minutes=10),
retry_policy=retry,
)
# 保存结果
url = await workflow.execute_activity(
save_result,
result,
start_to_close_timeout=timedelta(minutes=5),
retry_policy=retry,
)
# 发通知
await workflow.execute_activity(
send_email,
f"Data {data_id} processed: {url}",
start_to_close_timeout=timedelta(seconds=30),
)
return url
Serverless Event-Driven Durable
Inngest 是面向 Serverless 环境的持久化执行平台。它把 Temporal 的 Durable Execution 概念搬到了 Serverless 世界——你写普通的函数,Inngest 处理重试、超时、并发、限流,而且不需要运行任何服务器。
Inngest 的核心模型是 Event → Function:
user.created, order.paid)与 Temporal 的关键区别:Inngest 的 Function 跑在你的 Serverless 函数里(Vercel、Netlify、AWS Lambda),Inngest 只负责调度和状态管理。你不需要运行 Worker 进程。
传统队列:
App → Queue → Worker Process → 执行
需要运行 Worker 进程,需要管理 Worker 生命周期
Inngest:
App → Inngest Cloud → HTTPS 调用你的 Serverless Function → 执行
不需要 Worker,不需要管理进程,但需要 HTTPS 端点
// inngest/functions.ts
import { inngest } from "./client";
// 用户注册后的多步处理
export const onUserCreated = inngest.createFunction(
{
id: "on-user-created",
name: "User Onboarding Flow",
retries: 3, // 最多重试 3 次
concurrency: { // 同一用户串行执行
limit: 1,
key: "event.data.userId",
},
throttle: { // 限流
limit: 100,
period: "1m",
},
},
{ event: "user.created" }, // 触发 Event
async ({ event, step }) => {
const { userId, email, name } = event.data;
// Step 1: 发送欢迎邮件
await step.run("send-welcome-email", async () => {
await sendEmail(email, "welcome", { name });
return { sent: true };
});
// Step 2: 等待 1 天
await step.sleep("wait-1-day", "1d");
// Step 3: 发送引导邮件
await step.run("send-onboarding-email", async () => {
const hasLoggedIn = await checkLogin(userId);
if (!hasLoggedIn) {
await sendEmail(email, "onboarding-reminder", { name });
}
return { sent: !hasLoggedIn };
});
// Step 4: 等待 3 天
await step.sleep("wait-3-days", "3d");
// Step 5: 检查是否激活
const isActive = await step.run("check-activation", async () => {
const user = await getUser(userId);
return user.lastActiveAt > user.createdAt;
});
if (!isActive) {
await step.run("send-re-engagement", async () => {
await sendEmail(email, "re-engagement", { name });
});
}
return { userId, onboardingComplete: true };
}
);
// 从你的应用中发送 Event
import { inngest } from "./client";
// 用户注册时
app.post("/register", async (req, res) => {
const user = await createUser(req.body);
// 发送 Event → Inngest 调度 Function
await inngest.send({
name: "user.created",
data: {
userId: user.id,
email: user.email,
name: user.name,
},
});
res.json({ user }); // 立即返回,不等后台处理
});
// 也可以批量发送
await inngest.send([
{ name: "order.created", data: { orderId: "1", ... } },
{ name: "order.created", data: { orderId: "2", ... } },
]);
// 定时任务
export const dailyCleanup = inngest.createFunction(
{ id: "daily-cleanup", name: "Daily Cleanup" },
{ cron: "0 3 * * *" }, // 每天凌晨 3 点
async ({ step }) => {
await step.run("cleanup-expired-sessions", async () => {
const count = await Session.deleteExpired();
return { deleted: count };
});
await step.run("cleanup-temp-files", async () => {
const count = await TempFile.deleteOlderThan(7);
return { deleted: count };
});
}
);
# functions.py
from inngest import Inngest, Function, Event, Step
client = Inngest(app_id="my-saas")
@client.create_function(
fn_id="process-upload",
trigger=Event(event="file.uploaded"),
retries=3,
)
async def process_upload(ctx: dict, step: Step):
file_url = ctx["event"]["data"]["url"]
# Step 1: 验证文件
validated = await step.run("validate", async () => {
return await validate_file(file_url)
})
if not validated["ok"]:
return {"status": "rejected", "reason": validated["reason"]}
# Step 2: 处理文件
result = await step.run("process", async () => {
return await process_file(file_url)
})
# Step 3: 通知用户
await step.run("notify", async () => {
await send_notification(
ctx["event"]["data"]["userId"],
f"File processed: {result['output_url']}"
)
})
return {"status": "done", "output_url": result["output_url"]}
| 维度 | RQ | BullMQ | Celery | Temporal | Inngest |
|---|---|---|---|---|---|
| 语言 | Python | Node.js/TS | Python | Go/TS/Python/Java | TS/Python/Go |
| Broker | Redis | Redis | Redis/RabbitMQ/SQS | 自管理 Server | Inngest Cloud |
| 基础设施 | Redis + Worker | Redis + Worker | Broker + Worker + Backend | Server + DB + Worker | 无(Serverless) |
| 编排能力 | ❌ 无 | ⚡ Flow(父子任务) | ✅ Chain/Group/Chord | ✅✅ 完整 Workflow | ✅✅ Step 编排 |
| Durable | ❌ | ❌ | ❌ | ✅✅ 崩溃恢复 | ✅ Step 级 |
| 重试策略 | 固定次数 | 指数退避 | 指数退避 | 指数退避 + 自定义 | 指数退避 |
| 定时任务 | rq-scheduler | ✅ Repeatable | ✅ Celery Beat | ✅ Schedule | ✅ Cron |
| 优先级 | 多队列模拟 | ✅ 原生 | 多队列模拟 | 多队列模拟 | ✅ 原生 |
| 限流 | ❌ | ✅ Worker 级 | ❌(需 celery-throttle) | ✅ Activity 级 | ✅ Function 级 |
| 监控 | rq-dashboard | Bull Board | Flower | Web UI | Dashboard |
| 学习曲线 | ⭐ 极低 | ⭐⭐ 低 | ⭐⭐⭐ 中 | ⭐⭐⭐⭐ 高 | ⭐⭐ 低 |
| 生产复杂度 | 低 | 中 | 中-高 | 高 | 低 |
| 适用规模 | 小型 | 中-大型 | 大型 | 企业级 | 中小型 |
| 定价 | 免费 | 免费 | 免费 | 免费 / Temporal Cloud | 免费额度 / 按用量 |
所有"至少一次"交付的系统都要求消费者幂等——同一任务执行多次,结果相同。
# ❌ 非幂等:每次执行都创建新记录
def process_payment(order_id):
order = Order.get(order_id)
charge = Charge.create(amount=order.total) # 重试会重复扣款!
return charge.id
# ✅ 幂等:先检查是否已处理
def process_payment(order_id):
order = Order.get(order_id)
# 检查是否已有成功的扣款
existing = Charge.find_by_order(order_id, status="succeeded")
if existing:
return existing.id # 已处理过,直接返回
charge = Charge.create(amount=order.total, idempotency_key=order_id)
return charge.id
# ✅ 更好:用数据库唯一约束保证幂等
def process_payment(order_id):
try:
charge = Charge.create(
amount=Order.get(order_id).total,
idempotency_key=f"order-{order_id}" # UNIQUE 约束
)
except UniqueViolation:
# 已经创建过,返回已有的
charge = Charge.find_by_idempotency_key(f"order-{order_id}")
return charge.id
微服务中,跨服务的事务不能用数据库事务。Saga Pattern 的思路:每一步都有对应的补偿操作,某步失败时,逆序执行之前步骤的补偿。
# Temporal 实现 Saga Pattern
@workflow.defn
class OrderSagaWorkflow:
@workflow.run
async def run(self, order: Order):
# 补偿栈——失败时逆序执行
compensations = []
try:
# Step 1: 预留库存
await workflow.execute_activity(reserve_inventory, order.items)
compensations.append(("release_inventory", order.items))
# Step 2: 扣款
charge_id = await workflow.execute_activity(charge_payment, order)
compensations.append(("refund_payment", charge_id))
# Step 3: 创建发货单
shipment_id = await workflow.execute_activity(create_shipment, order)
compensations.append(("cancel_shipment", shipment_id))
# 全部成功
return {"status": "completed", "shipment_id": shipment_id}
except Exception:
# 逆序执行补偿
for comp_name, comp_arg in reversed(compensations):
try:
await workflow.execute_activity(
get_activity(comp_name), comp_arg,
start_to_close_timeout=timedelta(seconds=30),
)
except Exception as comp_err:
# 补偿也失败!→ 人工干预
logger.error(f"Compensation failed: {comp_err}")
await workflow.execute_activity(
alert_human, order.id, comp_name, comp_err
)
raise
当任务产生速度 > 消费速度时,需要限流和背压机制防止系统崩溃。
// BullMQ: Worker 级限流
const worker = new Worker('api-calls', async job => {
return await callExternalAPI(job.data);
}, {
limiter: {
max: 10, // 每秒最多 10 个
duration: 1000,
},
concurrency: 5, // 同时处理 5 个
});
// BullMQ: Queue 级限流(防止队列无限增长)
await queue.add('api-call', data, {
lifo: false, // FIFO(先进先出)
});
// 检查队列深度——背压
const counts = await queue.getJobCounts();
if (counts.waiting > 10000) {
// 队列积压过多,拒绝新任务或降级
throw new ServiceUnavailableError("Queue overloaded, try again later");
}
# Celery: 多队列模拟优先级
# 定义多个队列
app.conf.task_queues = {
'critical': Queue('critical', routing_key='critical'),
'default': Queue('default', routing_key='default'),
'low': Queue('low', routing_key='low'),
}
# 指定任务队列
@shared_task(queue='critical')
def send_password_reset(email):
... # 密码重置邮件,高优先级
@shared_task(queue='low')
def generate_weekly_report():
... # 周报,低优先级
# Worker 监听多个队列(按顺序消费)
# $ celery -A proj worker -Q critical,default,low
# 会先消费 critical,然后 default,最后 low
# Celery: 处理死信
@shared_task(bind=True, max_retries=3)
def risky_task(self, data):
try:
return do_something_risky(data)
except Exception as exc:
if self.request.retries >= self.max_retries:
# 重试耗尽 → 转入死信处理
handle_dead_letter.delay(str(self.request.id), data, str(exc))
return {"status": "dead_lettered", "error": str(exc)}
raise self.retry(exc=exc)
@shared_task
def handle_dead_letter(task_id, data, error):
"""死信处理:记录 + 告警"""
DeadLetterRecord.create(
task_id=task_id,
data=data,
error=error,
timestamp=datetime.utcnow(),
)
# 发 Slack 告警
slack_notify(f"💀 Task {task_id} dead-lettered: {error}")
所有基于"至少一次"交付的队列都可能重复执行任务。如果任务有副作用(扣款、发邮件、创建记录),必须保证幂等。最常见的翻车场景:支付重试导致双重扣款。
解法:唯一约束 + 幂等 Key + 先查后写。
Worker 取了任务但崩溃了,任务既没有完成也没有失败——卡在"active"状态。BullMQ 默认的 lockDuration 是 30 秒,之后会自动放回队列。Celery 需要配置 task_acks_late=True + task_reject_on_worker_lost=True。
解法:配置 acks_late、设置合理的 visibility_timeout、启用 stale job 检测。
任务积累过多,Redis 内存爆了。常见于消费者故障或任务突增的场景。
解法:监控队列深度、设置 maxmemory-policy(如 allkeys-lru)、实现背压(队列过深时拒绝入队)。
下游服务故障 → 大量任务失败 → 全部重试 → 下游更加过载 → 更多失败。指数退避可以缓解但不能根治。
解法:Circuit Breaker(熔断器)+ 限流 + 分级重试(关键任务优先重试,非关键延迟重试)。
Celery 的 Chord(并行 + 回调)在 Redis Broker 下有已知的竞态条件——回调可能丢失。这在某些 Celery 版本中已修复,但仍需注意。
解法:升级 Celery / 使用 RabbitMQ(Chord 实现更可靠)/ 改用链式 Group。
Celery 默认用 pickle 序列化——安全风险(反序列化漏洞)且不可跨语言。RQ 也用 pickle。
解法:始终配置 task_serializer='json' + accept_content=['json']。只传递可 JSON 序列化的数据。
Temporal Workflow 必须是确定性的——不能用 Math.random()、new Date()、发 HTTP 请求。否则 Replay 时结果不一致,导致状态损坏。
解法:随机用 workflow.uuid4(),时间用 workflow.now(),副作用放 Activity 里。
异步任务队列在 Agent 系统中扮演关键角色——Agent 的执行本身就是异步任务。从 Agent 研究报告中可以看到:
| Agent 项目 | 队列/异步机制 | 关联场景 |
|---|---|---|
| OpenClaw | Session + Cron + Heartbeat | Agent 会话是持久化异步任务;Cron 触发定时 Agent 执行;Heartbeat 是周期性轮询 |
| Agent Zero | 多轮 ReAct 循环 | 每轮 LLM 调用 + 工具执行是一个异步 Step,需要超时和重试 |
| AutoGen | Actor Model + 消息传递 | Agent 间通信就是异步消息队列——Actor 收到消息后处理,天然解耦 |
| LangGraph | State + Checkpoint | Graph 的每个节点是一个 Step,Checkpoint 就是持久化——类似 Temporal 的 Event Sourcing |
| GPT Researcher | 并行搜索 + 汇总 | 多个搜索任务并行执行(Group),然后汇总(Chord)——经典 MapReduce 模式 |
| OpenHands | Event Stream | Agent 行为通过 Event Stream 传递——类似消息队列,Consumer(Agent)订阅处理 |