← 返回 SaaS 知识库

⚙️ 异步任务与队列

Redis Queue / BullMQ / Celery / Temporal / Inngest — 当「请求-响应」不够用时,如何构建可靠的异步处理系统

📑 目录

  1. 为什么需要异步任务
  2. 核心概念
  3. Redis Queue (Python)
  4. BullMQ (Node.js)
  5. Celery (Python)
  6. Temporal (多语言)
  7. Inngest (Serverless)
  8. 全维度对比
  9. 选型决策树
  10. 实战模式
  11. 常见坑与教训
  12. 与 Agent 架构的关联

1. 为什么需要异步任务

HTTP 请求-响应模型有一个硬限制:用户不想等。当一个操作需要几秒甚至几分钟——发邮件、生成报表、视频转码、AI 推理——同步处理会让请求超时、连接断开、用户体验灾难。

同步处理:用户 → API → [处理 30 秒...] → 响应 → 用户等到崩溃 异步处理:用户 → API → [入队,立即返回 task_id] → 用户自由活动 ↓ Worker → [处理 30 秒...] → 完成 → 通知用户

典型的异步场景

💡 判断标准:如果一个操作耗时 >500ms 或可能失败需要重试,就应该考虑异步化。

2. 核心概念

在深入各个工具之前,先理解异步任务系统的通用概念——这些概念在不同工具中以不同名字出现,但本质相同。

2.1 Producer → Queue → Consumer 模型

┌──────────┐ ┌───────────────┐ ┌──────────┐ │ Producer │ ──→ │ Queue │ ──→ │ Consumer │ │ (生产者) │ │ (消息队列) │ │ (消费者) │ └──────────┘ └───────────────┘ └──────────┘ ↑ ↓ ↓ API 请求 持久化存储 执行任务 入队操作 (Redis/DB/内存) 更新结果

这是最基本的模型。Producer(生产者)将任务描述序列化后放入 Queue(队列),Consumer(消费者/Worker)从队列取出任务执行。生产者和消费者解耦,可以独立扩展。

2.2 关键术语

📖 术语表

术语含义不同工具的叫法
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 协议,所有系统都有类似机制

2.3 三个层次的"可靠"

层次 1:At-Most-Once(最多一次)

任务可能丢失,但不会重复。适用于「丢了也无妨」的场景(如日志处理)。实现最简单——fire and forget。

层次 2:At-Least-Once(至少一次)

任务不会丢失,但可能重复执行。绝大多数队列系统默认提供。需要消费者端幂等。

层次 3:Exactly-Once(恰好一次)

任务既不丢失也不重复。理论上很难(FLP 不可能定理),实践中通过幂等 + 去重 + 事务近似实现。Temporal 的 Workflow 在这方面做得最好。

3. Redis Queue (Python)

Python 轻量 Redis

RQ (Redis Queue) 是 Python 生态最简单的任务队列——直接用 Redis 的 LPUSH/BRPOP 实现队列,没有额外依赖,5 分钟上手。

3.1 是什么

RQ 是一个极简的 Python 任务队列库,将函数调用序列化为 Job 存入 Redis,Worker 进程从 Redis 取出执行。它的哲学是:简单到不需要学习

3.2 何时用

3.3 优劣对比

✅ 优点

  • 极简 API,5 分钟上手
  • 纯 Python,代码可读性高
  • 任务就是普通函数,测试方便
  • 内置 Flask/Django 集成
  • 支持定时任务(rq-scheduler)
  • 任务结果存 Redis,查询方便

❌ 缺点

  • 仅支持 Redis 作为 Broker
  • 不支持任务编排(链式、DAG)
  • 重试策略简单(固定次数)
  • 无优先级队列(只有不同队列名)
  • 监控工具简陋(rq-dashboard 基础)
  • Worker 是 fork 模型,内存开销大
  • 不适合高吞吐(单 Redis 可能成为瓶颈)

3.4 实际代码示例

定义任务(就是普通函数)

# 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}

入队(Producer 端)

# 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

# 命令行启动 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  # 处理完所有任务后退出(适合批处理)

3.5 替代方案

4. BullMQ (Node.js)

Node.js Redis 功能丰富

BullMQ 是 Node.js 生态中最强大的 Redis 队列库。它基于 Bull(v3)重写,支持优先级、延迟、限流、父子任务、进度追踪等高级特性,是 NestJS、Vercel 等项目的底层选择。

4.1 是什么

BullMQ 是一个基于 Redis 的高性能任务队列,专为 Node.js/TypeScript 设计。它将 Redis 用到极致——不是简单的 LPUSH/BRPOP,而是利用 Redis 的 Hash、Sorted Set、List、Stream 等多种数据结构实现优先级队列、延迟队列、限流、事件通知等功能。

🔧 Redis 数据结构的使用

4.2 何时用

4.3 优劣对比

✅ 优点

  • TypeScript 一等支持,类型安全
  • 优先级队列、延迟、限流开箱即用
  • 父子任务(Job Chaining / Flow)
  • 实时进度追踪(job.updateProgress)
  • 事件驱动(completed, failed, progress, waiting...)
  • Repeatable Jobs(定时任务)
  • 自动重试 + 指数退避
  • Bull Board(可视化监控面板)
  • NestJS 官方集成
  • 社区活跃,文档优秀

❌ 缺点

  • 仅支持 Redis(单点依赖)
  • Redis 内存成本(大量任务时需注意)
  • 不是分布式 Workflow 引擎(不如 Temporal)
  • Worker 崩溃时任务可能卡在 active 状态(需配置 staleJobs)
  • 跨队列编排复杂(Flow 有局限性)
  • Cluster 模式下需要 Redis Cluster

4.4 实际代码示例

基本用法

// 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(任务编排)

// 用 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
// 父任务等所有子任务完成后才执行

Bull Board 监控

// 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 即可查看任务状态、重试、删除

4.5 替代方案

5. Celery (Python)

Python 重量级 企业级

Celery 是 Python 生态中最老牌、最成熟的分布式任务队列。它支持多种 Broker(Redis、RabbitMQ、SQS)和多种 Result Backend,是 Django 项目的默认选择。

5.1 是什么

Celery 是一个分布式任务队列系统,核心由三部分组成:

与 RQ 不同,Celery 提供了完整的任务编排能力:Chain(链式)、Group(并行)、Chord(并行+回调)、Chunk(分块)。

5.2 何时用

5.3 优劣对比

✅ 优点

  • 最成熟稳定(2009 年至今,久经考验)
  • 多 Broker 支持(Redis/RabbitMQ/SQS/...)
  • 丰富的任务编排(Chain/Group/Chord/Chunk)
  • Celery Beat 定时调度(内置 Cron)
  • 多并发模型(prefork/gevent/eventlet/solo)
  • Django 深度集成
  • 信号系统(task_prerun, task_postrun, ...)
  • Flower 监控面板
  • 社区庞大,Stack Overflow 答案多
  • 任务序列化灵活(JSON/pickle/msgpack/yaml)

❌ 缺点

  • 配置复杂("配置地狱"不是开玩笑)
  • 文档虽全但组织混乱
  • Worker 进程内存开销大(prefork 模型)
  • Canvas 编排出错时调试困难
  • 不支持 Exactly-Once 语义
  • Chord 在某些 Broker 上有已知 Bug
  • AMQP 协议细节有时会"泄露"到用户层
  • 版本升级有时有破坏性变更
  • 不原生支持 Workflow 持久化(Worker 重启 = 状态丢失)

5.4 实际代码示例

配置

# 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}

任务编排(Canvas)

# 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()

Django 集成

# 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():
    ...

5.5 替代方案

6. Temporal (多语言)

Durable Execution 企业级 多语言

Temporal 不是传统的任务队列——它是持久化执行引擎(Durable Execution Engine)。代码写的像同步,执行的像异步,状态自动持久化,进程崩溃后自动恢复。这是处理复杂业务流程的终极武器。

6.1 是什么

Temporal 的核心思想:代码即 Workflow。你写普通的同步代码,Temporal 保证它最终一定会执行完成——即使进程崩溃、服务器宕机、网络分区。

🔑 关键概念

概念说明
Workflow一个持久化的执行流程。代码写的像普通函数,但每一步的状态都自动持久化。崩溃后从上次位置恢复。
ActivityWorkflow 中调用的具体操作(发邮件、调 API、操作 DB)。可重试、可超时、可取消。
Signal外部向运行中的 Workflow 发送异步消息。例如用户取消订单 → Workflow 收到 Signal → 逻辑分支变化。
Query外部向运行中的 Workflow 查询状态。不产生副作用,可以随时调用。
Timer持久化定时器。即使进程重启,Timer 也会在正确的时间触发。
Saga Pattern补偿事务模式。Workflow 中的每一步失败时,自动执行之前步骤的补偿操作。
💡 Temporal vs 传统队列:传统队列处理的是单个任务(发邮件)。Temporal 处理的是整个业务流程(下单 → 支付 → 确认 → 发货 → 通知),每一步可能失败,需要重试、超时、补偿、人工干预。流程可能运行几秒到几天。

6.2 何时用

6.3 优劣对比

✅ 优点

  • Durable Execution——代码像同步,执行像异步
  • 崩溃恢复——进程重启后自动从断点继续
  • 内置 Saga Pattern——补偿事务一行代码
  • Signal/Query——运行时交互式控制
  • 多语言 SDK(Go/TypeScript/Python/Java/...)
  • Web UI 监控——可视化每一步执行
  • Exactly-Once Activity 执行
  • 本地开发体验好(temporal cli dev server)
  • 可测试性强(Test Framework 模拟时间/Activity)
  • 社区活跃,商业化成熟

❌ 缺点

  • 架构复杂——需要 Temporal Server(独立服务)
  • 学习曲线陡峭——概念多(Workflow/Activity/Signal/Query/Timer)
  • Workflow 代码有限制(必须确定性——不能随机、不能调 API)
  • Event Sourcing 存储——历史数据量大
  • 自部署需要 Cassandra/PostgreSQL + Elasticsearch
  • 冷启动延迟高(短任务不如 BullMQ/Celery 轻量)
  • 不适合高吞吐的简单任务(杀鸡用牛刀)

6.4 实际代码示例

TypeScript SDK:订单处理 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 实现

// 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

Python SDK:简单示例

# 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

6.5 替代方案

7. Inngest (Serverless)

Serverless Event-Driven Durable

Inngest 是面向 Serverless 环境的持久化执行平台。它把 Temporal 的 Durable Execution 概念搬到了 Serverless 世界——你写普通的函数,Inngest 处理重试、超时、并发、限流,而且不需要运行任何服务器。

7.1 是什么

Inngest 的核心模型是 Event → Function

与 Temporal 的关键区别:Inngest 的 Function 跑在你的 Serverless 函数里(Vercel、Netlify、AWS Lambda),Inngest 只负责调度和状态管理。你不需要运行 Worker 进程。

🔄 Inngest vs 传统队列的架构差异

传统队列:
  App → Queue → Worker Process → 执行
  需要运行 Worker 进程,需要管理 Worker 生命周期

Inngest:
  App → Inngest Cloud → HTTPS 调用你的 Serverless Function → 执行
  不需要 Worker,不需要管理进程,但需要 HTTPS 端点

7.2 何时用

7.3 优劣对比

✅ 优点

  • 零基础设施——不需要 Worker 进程
  • Durable Execution(Step 级别自动重试)
  • Event-Driven——一个 Event 触发多个 Function
  • 内置限流/去重/优先级/并发控制
  • Declarative Concurrency——同一用户的 Function 串行
  • SDK 支持 TypeScript/Python/Go
  • Dev Server 本地调试
  • Dashboard 监控
  • Cron + Event 双触发
  • 免费额度慷慨(100K steps/月)

❌ 缺点

  • 依赖 Inngest Cloud(Vendor Lock-in)
  • 冷启动延迟(Serverless 函数的固有缺点)
  • Step 间有网络延迟(每次 Step 都是一次 HTTPS 调用)
  • 不适合 CPU 密集型任务(Serverless 有执行时间限制)
  • 免费额度后的定价可能不透明
  • 自部署选项尚不成熟
  • Function 必须暴露 HTTPS 端点

7.4 实际代码示例

TypeScript SDK:用户注册流程

// 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 发送

// 从你的应用中发送 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", ... } },
]);

Cron 定时

// 定时任务
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 };
    });
  }
);

Python SDK

# 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"]}

7.5 替代方案

8. 全维度对比

维度 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 免费额度 / 按用量

9. 选型决策树

🌲 从零开始怎么选?

Q1: 你的技术栈是什么?
→ Python → 继续 Q2
→ Node.js/TypeScript → 继续 Q3
→ Go / Java / 多语言 → 继续 Q4
Q2: Python 项目,需要什么级别的功能?
简单入队出队,5 分钟上手 → RQ
需要编排(Chain/Group)+ 定时任务 + 多 Broker → Celery
需要 Durable Execution + 崩溃恢复 → Temporal (Python SDK)
Serverless 架构,不想管 Worker → Inngest (Python SDK)
Q3: Node.js 项目,需要什么级别的功能?
标准任务队列 + 优先级 + 限流 → BullMQ
需要 Durable Execution + 工作流 → Temporal (TypeScript SDK)
Serverless,不想管基础设施 → Inngest
类似 Inngest 但要自部署 → Trigger.dev
Q4: 多语言 / 企业级
复杂业务流程 + 需要崩溃恢复 → Temporal
AWS 深度绑定 → AWS Step Functions
简单任务分发 → Celery(Python 为主)或 BullMQ(Node 为主)
⚠️ 常见误选:小项目用 Temporal(杀鸡用牛刀,运维成本 >> 开发收益);复杂流程用 RQ(很快遇到天花板,重构代价大)。先从简单方案开始,有需要再升级——RQ → Celery → Temporal 是自然的升级路径。

10. 实战模式

10.1 幂等性:异步任务的基石

所有"至少一次"交付的系统都要求消费者幂等——同一任务执行多次,结果相同。

# ❌ 非幂等:每次执行都创建新记录
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

10.2 Saga Pattern:分布式事务的补偿方案

微服务中,跨服务的事务不能用数据库事务。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

10.3 限流与背压

当任务产生速度 > 消费速度时,需要限流和背压机制防止系统崩溃。

// 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");
}

10.4 优先级队列

# 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

10.5 死信队列处理

# 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}")

11. 常见坑与教训

🕳️ 坑 1:忘记幂等性

所有基于"至少一次"交付的队列都可能重复执行任务。如果任务有副作用(扣款、发邮件、创建记录),必须保证幂等。最常见的翻车场景:支付重试导致双重扣款。

解法:唯一约束 + 幂等 Key + 先查后写。

🕳️ 坑 2:Worker 崩溃导致任务卡住

Worker 取了任务但崩溃了,任务既没有完成也没有失败——卡在"active"状态。BullMQ 默认的 lockDuration 是 30 秒,之后会自动放回队列。Celery 需要配置 task_acks_late=True + task_reject_on_worker_lost=True

解法:配置 acks_late、设置合理的 visibility_timeout、启用 stale job 检测。

🕳️ 坑 3:Redis OOM

任务积累过多,Redis 内存爆了。常见于消费者故障或任务突增的场景。

解法:监控队列深度、设置 maxmemory-policy(如 allkeys-lru)、实现背压(队列过深时拒绝入队)。

🕳️ 坑 4:重试风暴

下游服务故障 → 大量任务失败 → 全部重试 → 下游更加过载 → 更多失败。指数退避可以缓解但不能根治。

解法:Circuit Breaker(熔断器)+ 限流 + 分级重试(关键任务优先重试,非关键延迟重试)。

🕳️ 坑 5:Celery Chord Bug

Celery 的 Chord(并行 + 回调)在 Redis Broker 下有已知的竞态条件——回调可能丢失。这在某些 Celery 版本中已修复,但仍需注意。

解法:升级 Celery / 使用 RabbitMQ(Chord 实现更可靠)/ 改用链式 Group。

🕳️ 坑 6:序列化陷阱

Celery 默认用 pickle 序列化——安全风险(反序列化漏洞)且不可跨语言。RQ 也用 pickle。

解法:始终配置 task_serializer='json' + accept_content=['json']。只传递可 JSON 序列化的数据。

🕳️ 坑 7:Temporal Workflow 确定性

Temporal Workflow 必须是确定性的——不能用 Math.random()new Date()、发 HTTP 请求。否则 Replay 时结果不一致,导致状态损坏。

解法:随机用 workflow.uuid4(),时间用 workflow.now(),副作用放 Activity 里。

异步任务队列在 Agent 系统中扮演关键角色——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)订阅处理
💡 Agent + 队列的结合趋势:越来越多的 Agent 框架开始内置持久化执行。LangGraph 的 Checkpoint 是轻量版 Temporal;AutoGen 的 Actor Model 天然适配消息队列;OpenClaw 的 Session + Cron 机制本身就是一种异步任务系统。当你构建自己的 Agent 系统时,选择一个可靠的队列/持久化层是第一优先级

推荐组合

🧩 SaaS + Agent 的技术栈组合


← 返回 SaaS 知识库  |  Agent 研究报告  |  后端框架选型 →  |  缓存策略 →