# 非同步任務佇列：生產者消費者模型

::: tip 前言
**用户點了"導出报表"按钮，然後盯着轉圈的加載動画等了 30 秒——這合理吗？** 当一个操作需要几秒甚至几分鐘才能完成時，讓用户干等着顯然不是好體验。异步任務队列就是解决這个問题的核心架構模式——把耗時操作丟到後台去處理，讓用户立刻得到響應。
:::

**這篇文章會带你學什么？**

學完這章後，你将獲得：

- **同步异步對比**：理解為什么某些操作必须异步化，以及异步化带來的用户體验提升
- **生產消費模型**：掌握 Producer-Consumer 模式的核心思想和工作流程
- **Worker 池機制**：了解任務如何被分發到多个 Worker 并行處理
- **可靠性保障**：掌握任務重試、幂等性、死信队列等保障機制
- **技術選型能力**：了解主流异步任務框架的特點和適用場景

| 章節 | 內容 | 核心概念 |
|-----|------|---------|
| **第 1 章** | 為什么需要异步 | 同步阻塞 vs 异步非阻塞 |
| **第 2 章** | 生產消費模型 | Producer、Queue、Consumer |
| **第 3 章** | Worker 工作池 | 并發處理、任務分發 |
| **第 4 章** | 可靠性保障 | 重試策略、幂等性、死信队列 |
| **第 5 章** | 框架選型 | Celery、Sidekiq、Bull、RQ |

---

## 0. 全景图：非同步處理的必要性

想象你去餐厅點餐。好的餐厅會在你點完餐後立刻给你一个取餐号，然後你可以去找座位、玩手機，等餐好了再來取。而不是讓你站在柜台前，盯着厨师做完整道菜。

Web 應用中有很多類似的"做菜"操作：

- **發送郵件/短信**：調用第三方 API，可能需要几秒
- **生成报表/PDF**：大量數據計算，可能需要几十秒
- **图片/视频處理**：压缩、轉碼、加水印，可能需要几分鐘
- **數據同步**：跨系统數據同步，耗時不确定

::: tip 异步任務的核心思想
把耗時操作從"請求-響應"的主流程中剥離出來，放到後台队列中异步處理。用户提交請求後立刻得到"已收到，正在處理"的響應，處理完成後通過通知、輪询或 WebSocket 告知結果。
:::

---

## 1. 同步 vs 异步：一个订單的故事

当用户提交一个订單時，後端需要做很多事情：扣减庫存、創建订單記錄、發送确認郵件、更新推荐系统、記錄审計日志……

在同步模式下，這些操作串行執行，用户必须等所有操作完成才能看到結果。在异步模式下，只需要完成核心操作（扣减庫存、創建订單），其餘操作丟到队列裡後台處理。

<AsyncTaskFlowDemo />

| 對比維度 | 同步處理 | 异步處理 |
|---------|---------|---------|
| 用户等待時間 | 所有操作總耗時 | 僅核心操作耗時 |
| 系统吞吐量 | 低（线程被阻塞） | 高（快速釋放线程） |
| 失敗影響 | 非核心失敗導致整體失敗 | 非核心失敗不影響主流程 |
| 實現複雜度 | 简單 | 需要额外的队列基础設施 |
| 數據一致性 | 強一致 | 最终一致 |

::: tip 什么時候該用异步？
三个判断標準：**耗時長**（超過 1-2 秒）、**非核心**（失敗不應影響主流程）、**可延遲**（不需要立刻得到結果）。满足其中任意兩个，就應該考虑异步化。
:::

---

## 2. 生產消費模型：任務的"流水线"

异步任務队列的核心是經典的 **生產者-消費者模式（Producer-Consumer Pattern）**。這个模式有三个角色：

- **生產者（Producer）**：產生任務的一方，通常是 Web 服務器處理用户請求時
- **队列（Queue）**：存儲待處理任務的緩衝區，通常用 Redis、RabbitMQ 等實現
- **消費者（Consumer/Worker）**：從队列中取出任務并執行的工作進程

<TaskWorkerDemo />

::: tip 队列的三大价值
1. **解耦**：生產者不需要知道誰來處理任務，消費者不需要知道任務從哪來
2. **削峰填谷**：突發流量時任務先堆积在队列中，消費者按自己的節奏處理
3. **可靠性**：任務持久化在队列中，即使消費者崩溃也不會丟失
:::

| 組件 | 职责 | 常见實現 |
|------|------|---------|
| 消息中間件 | 存儲和轉發任務消息 | Redis、RabbitMQ、Kafka |
| 序列化器 | 将任務參數序列化/反序列化 | JSON、MessagePack、Pickle |
| 調度器 | 管理定時任務和延遲任務 | Cron、APScheduler、node-cron |
| 結果存儲 | 保存任務執行結果 | Redis、數據庫、S3 |

---

## 3. 可靠性保障：任務不能"丟了"也不能"重複"

在分布式環境中，網絡抖動、服務重启、资源不足等問题隨時可能發生。异步任務系统必须具備完善的可靠性保障機制。

最核心的兩个問题：**任務丟失**（消費者處理到一半崩溃了）和**重複執行**（任務被投遞了兩次）。

<TaskRetryDemo />

::: tip 可靠性三板斧
1. **ACK 機制**：消費者處理完任務後才發送确認（ACK），未确認的任務會被重新投遞
2. **重試策略**：任務失敗後按策略重試，指數退避 + 抖動是最佳實踐
3. **幂等性設計**：同一个任務執行多次和執行一次的效果相同，通過唯一 ID 去重實現
:::

| 機制 | 解决的問题 | 實現方式 |
|------|-----------|---------|
| ACK 确認 | 任務丟失 | 處理完成後手動确認，超時未确認则重新投遞 |
| 死信队列（DLQ） | 反複失敗的"毒消息" | 重試超過上限後轉入死信队列，人工介入處理 |
| 幂等性 | 重複執行 | 用任務唯一 ID 做去重，數據庫唯一约束 |
| 優先级队列 | 任務饥餓 | 高優先级任務優先處理，避免被低優先级任務阻塞 |
| 超時控制 | 任務卡死 | 設置最大執行時間，超時自動终止并重試 |

---

## 4. 框架選型：選择適合你的工具

不同語言生態有不同的异步任務框架，它们在功能豐富度、性能、易用性上各有侧重。選择框架時，首先考虑你的技術栈，然後根據项目規模和需求做决定。

<AsyncComparisonDemo />

::: tip 選型建议
- **Python 项目**：中大型用 Celery，小型用 RQ
- **Node.js 项目**：首選 BullMQ（Bull 的下一代）
- **Ruby 项目**：Sidekiq 几乎是唯一選择
- **Java 项目**：Spring 生態用 Spring Batch，高吞吐用 Kafka Streams
- **Go 项目**：Asynq（基于 Redis）或 Machinery

如果你的项目已經在用 Redis，那么基于 Redis 的方案（Celery+Redis、BullMQ、Sidekiq）是最简單的起步方式。
:::

---

## 總結

异步任務队列是後端架構中不可或缺的基础設施。它讓系统能够優雅地處理耗時操作，提升用户體验的同時提高系统吞吐量。

回顧本章的關鍵要點：

1. **异步化的判断標準**：耗時長、非核心、可延遲，满足兩个就該异步化
2. **生產消費模型**：Producer → Queue → Consumer，三者解耦協作
3. **Worker 池**：多个 Worker 并行消費，提高處理能力
4. **可靠性保障**：ACK 确認 + 重試策略 + 幂等性，三者缺一不可
5. **框架選型**：根據技術栈和项目規模選择，Redis 是最常见的消息中間件

## 延伸阅讀

- [Celery 官方文檔](https://docs.celeryq.dev/) - Python 最流行的分布式任務队列
- [BullMQ 文檔](https://docs.bullmq.io/) - Node.js 高性能任務队列
- [Sidekiq Wiki](https://github.com/sidekiq/sidekiq/wiki) - Ruby 生態的任務處理標杆
- [RabbitMQ Tutorials](https://www.rabbitmq.com/tutorials) - 消息中間件入門教程
- [异步任務最佳實踐](https://brandur.org/job-drain) - 任務队列的設計模式與陷阱
