Python观察者模式:用RabbitMQ+Celery实现事件驱动
前言
这篇是设计模式小册系列的学习笔记,这次整理的是观察者模式以及如何用 RabbitMQ + Celery 实现分布式事件驱动架构。
你可能天天在用观察者模式但没意识到------用户注册后发邮件、订单创建后扣库存、支付成功后发通知,这些"一个动作触发多个后续操作"的场景,本质上都是观察者模式的应用。
观察者模式的核心思想是解耦:触发事件的代码不需要知道有哪些后续操作,它只管发出"事件",至于谁来处理、怎么处理,它不关心。听起来有点像订阅 UP 主?没错,就是这个道理。
这篇文章会从最简单的 Python 实现开始,然后引入 RabbitMQ + Celery,展示如何在分布式环境下实现事件驱动架构。
🏠个人主页:山沐与山
文章目录
- 一、观察者模式基础
- 二、Python实现观察者模式
- 三、进阶:事件总线
- [四、RabbitMQ + Celery分布式实现](#四、RabbitMQ + Celery分布式实现)
- 五、FastAPI实战整合
- 六、同步vs异步观察者对比
- 七、常见问题
- 八、总结
一、观察者模式基础
1.1 什么是观察者模式?
观察者模式定义了对象间的一对多依赖关系:当"被观察者"(Subject)状态改变时,所有"观察者"(Observer)都会收到通知并自动更新。
生活中的例子:你订阅了一个 UP 主,UP 主发新视频时,所有订阅者都会收到通知。UP 主不需要知道有哪些人订阅了,只管发视频就行。看到没?这就是解耦。
| 角色 | 职责 | 生活类比 |
|---|---|---|
Subject(被观察者) |
维护观察者列表,状态变化时通知所有观察者 | UP 主 |
Observer(观察者) |
定义更新接口,接收通知后执行操作 | 订阅者 |
ConcreteSubject |
具体的被观察者实现 | 具体的某个UP主 |
ConcreteObserver |
具体的观察者实现 | 具体的某个粉丝 |
1.2 不用观察者模式会怎样?
来看一个反面教材:
python
class OrderService:
"""订单服务 - 反面教材"""
def create_order(self, order_data):
# 创建订单
order = Order(**order_data)
self.order_repo.add(order)
# 问题来了:后续操作全写死在这里
self.inventory_service.decrease_stock(order) # 扣库存
self.email_service.send_confirmation(order) # 发邮件
self.sms_service.send_notification(order) # 发短信
self.analytics_service.track_order(order) # 数据统计
self.reward_service.add_points(order) # 加积分
return order
累不累?问题很明显:
| 问题 | 具体表现 | 后果 |
|---|---|---|
| 紧耦合 | OrderService 依赖一堆其他服务 |
改一个服务可能影响订单服务 |
| 难扩展 | 新增后续操作要改 create_order 方法 |
违反开闭原则 |
| 难维护 | 一个方法做太多事 | 代码臃肿,难以测试 |
| 性能差 | 所有操作串行执行 | 用户等待时间长 |
| 难测试 | 测试订单创建要 mock 一堆服务 | 测试成本高 |
二、Python实现观察者模式
2.1 经典实现:基于抽象类
python
from abc import ABC, abstractmethod
from typing import List, Any
class Observer(ABC):
"""
观察者抽象类
所有具体观察者都要实现 update 方法
"""
@abstractmethod
def update(self, event: str, data: Any) -> None:
"""接收通知并处理"""
pass
class Subject:
"""
被观察者(事件发布者)
维护观察者列表,提供订阅/取消订阅/通知方法
"""
def __init__(self):
self._observers: List[Observer] = []
def attach(self, observer: Observer) -> None:
"""订阅:添加观察者"""
if observer not in self._observers:
self._observers.append(observer)
print(f"[+] 观察者 {observer.__class__.__name__} 已订阅")
def detach(self, observer: Observer) -> None:
"""取消订阅:移除观察者"""
self._observers.remove(observer)
print(f"[-] 观察者 {observer.__class__.__name__} 已取消订阅")
def notify(self, event: str, data: Any = None) -> None:
"""通知所有观察者"""
print(f"\n>>> 发布事件: {event}")
for observer in self._observers:
observer.update(event, data)
然后是具体实现:
python
# ========== 具体的被观察者 ==========
class OrderSubject(Subject):
"""订单事件发布者"""
def create_order(self, order_data: dict):
# 核心业务:创建订单
order = {"id": 1001, **order_data}
print(f"[订单] 创建成功: {order}")
# 只管发通知,不管谁来处理
self.notify("order_created", order)
return order
def pay_order(self, order_id: int):
order = {"id": order_id, "status": "paid"}
print(f"[支付] 订单支付成功: {order_id}")
self.notify("order_paid", order)
return order
# ========== 具体的观察者 ==========
class EmailObserver(Observer):
"""邮件观察者:负责发送邮件通知"""
def update(self, event: str, data: Any) -> None:
if event == "order_created":
print(f" [邮件] 发送订单确认邮件: 订单#{data['id']}")
elif event == "order_paid":
print(f" [邮件] 发送支付成功邮件: 订单#{data['id']}")
class InventoryObserver(Observer):
"""库存观察者:负责扣减库存"""
def update(self, event: str, data: Any) -> None:
if event == "order_created":
print(f" [库存] 扣减库存: 订单#{data['id']}")
class AnalyticsObserver(Observer):
"""数据分析观察者:负责记录统计数据"""
def update(self, event: str, data: Any) -> None:
if event == "order_created":
print(f" [统计] 记录订单数据: 订单#{data['id']}, 金额: {data.get('amount', 0)}")
elif event == "order_paid":
print(f" [统计] 记录支付数据: 订单#{data['id']}")
class PointsObserver(Observer):
"""积分观察者:负责添加用户积分"""
def update(self, event: str, data: Any) -> None:
if event == "order_paid":
points = int(data.get("amount", 0) / 10)
print(f" [积分] 增加用户积分: {points} 分")
使用方式:
python
# 创建被观察者
order_subject = OrderSubject()
# 注册观察者(订阅)
order_subject.attach(EmailObserver())
order_subject.attach(InventoryObserver())
order_subject.attach(AnalyticsObserver())
order_subject.attach(PointsObserver())
# 创建订单,自动触发所有观察者
order_subject.create_order({
"product": "iPhone 15",
"amount": 9999,
"user_id": 123
})
输出:
[+] 观察者 EmailObserver 已订阅
[+] 观察者 InventoryObserver 已订阅
[+] 观察者 AnalyticsObserver 已订阅
[+] 观察者 PointsObserver 已订阅
[订单] 创建成功: {'id': 1001, 'product': 'iPhone 15', 'amount': 9999, 'user_id': 123}
>>> 发布事件: order_created
[邮件] 发送订单确认邮件: 订单#1001
[库存] 扣减库存: 订单#1001
[统计] 记录订单数据: 订单#1001, 金额: 9999
看到没?OrderSubject 完全不知道有哪些观察者,它只管发事件。新增后续操作?创建一个新的 Observer 然后 attach 就行,不用改 OrderSubject 的代码。
2.2 更Pythonic的实现:回调函数
不一定要用类,函数也行。这种方式更轻量:
python
from typing import Callable, Dict, List, Any
from dataclasses import dataclass
@dataclass
class Event:
"""事件对象:封装事件名称和数据"""
name: str
data: Any
class EventEmitter:
"""
事件发射器
类似 Node.js 的 EventEmitter,用回调函数作为监听器
"""
def __init__(self):
# 事件名 -> 回调函数列表
self._listeners: Dict[str, List[Callable]] = {}
def on(self, event_name: str, callback: Callable) -> "EventEmitter":
"""
注册监听器
支持链式调用
"""
if event_name not in self._listeners:
self._listeners[event_name] = []
self._listeners[event_name].append(callback)
print(f"[+] 注册监听器: {callback.__name__} -> {event_name}")
return self # 支持链式调用
def off(self, event_name: str, callback: Callable) -> "EventEmitter":
"""移除监听器"""
if event_name in self._listeners:
self._listeners[event_name].remove(callback)
return self
def emit(self, event_name: str, data: Any = None) -> None:
"""触发事件,调用所有监听器"""
if event_name not in self._listeners:
return
print(f"\n>>> 触发事件: {event_name}")
event = Event(event_name, data)
for callback in self._listeners[event_name]:
try:
callback(event)
except Exception as e:
print(f"[x] 监听器 {callback.__name__} 执行失败: {e}")
def once(self, event_name: str, callback: Callable) -> "EventEmitter":
"""
注册一次性监听器
触发一次后自动移除
"""
def wrapper(event: Event):
callback(event)
self.off(event_name, wrapper)
return self.on(event_name, wrapper)
使用起来更简洁:
python
# 创建事件发射器
emitter = EventEmitter()
# 用函数作为监听器
def send_email(event: Event):
print(f" [邮件] 发送邮件: 订单#{event.data['id']}")
def update_inventory(event: Event):
print(f" [库存] 更新库存: 订单#{event.data['id']}")
def add_points(event: Event):
points = int(event.data.get("amount", 0) / 10)
print(f" [积分] 增加积分: {points} 分")
# 链式注册
emitter.on("order_created", send_email) \
.on("order_created", update_inventory) \
.on("order_paid", send_email) \
.on("order_paid", add_points)
# 触发事件
emitter.emit("order_created", {"id": 1001, "amount": 9999})
emitter.emit("order_paid", {"id": 1001, "amount": 9999})
输出:
[+] 注册监听器: send_email -> order_created
[+] 注册监听器: update_inventory -> order_created
[+] 注册监听器: send_email -> order_paid
[+] 注册监听器: add_points -> order_paid
>>> 触发事件: order_created
[邮件] 发送邮件: 订单#1001
[库存] 更新库存: 订单#1001
>>> 触发事件: order_paid
[邮件] 发送邮件: 订单#1001
[积分] 增加积分: 999 分
两种实现方式的对比:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基于类 | 结构清晰,符合 OOP | 代码量多,有点重 | 复杂业务,需要状态的观察者 |
| 回调函数 | 轻量,灵活,Pythonic | 不好管理复杂逻辑 | 简单场景,临时处理 |
三、进阶:事件总线
在实际项目中,我们通常用一个全局的"事件总线"来管理所有事件。这样各个模块都可以发布和订阅事件,不需要互相依赖。
3.1 领域事件定义
python
from typing import Callable, Dict, List, Any, Type
from dataclasses import dataclass, field
from datetime import datetime
import asyncio
@dataclass
class DomainEvent:
"""
领域事件基类
所有业务事件都继承这个类
"""
occurred_at: datetime = field(default_factory=datetime.now)
def __post_init__(self):
# 确保事件创建时间
if self.occurred_at is None:
self.occurred_at = datetime.now()
@dataclass
class OrderCreatedEvent(DomainEvent):
"""订单创建事件"""
order_id: int = 0
user_id: int = 0
amount: float = 0.0
items: list = field(default_factory=list)
@dataclass
class OrderPaidEvent(DomainEvent):
"""订单支付事件"""
order_id: int = 0
payment_id: str = ""
amount: float = 0.0
@dataclass
class OrderCancelledEvent(DomainEvent):
"""订单取消事件"""
order_id: int = 0
reason: str = ""
@dataclass
class UserRegisteredEvent(DomainEvent):
"""用户注册事件"""
user_id: int = 0
email: str = ""
username: str = ""
3.2 事件总线实现
python
class EventBus:
"""
事件总线(单例模式)
全局唯一,管理所有事件的发布和订阅
"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._handlers: Dict[Type, List[Callable]] = {}
cls._instance._async_handlers: Dict[Type, List[Callable]] = {}
return cls._instance
def subscribe(self, event_type: Type[DomainEvent], handler: Callable) -> None:
"""
订阅事件
handler 是一个接收事件对象的函数
"""
if event_type not in self._handlers:
self._handlers[event_type] = []
self._handlers[event_type].append(handler)
print(f"[+] 订阅: {handler.__name__} -> {event_type.__name__}")
def subscribe_async(self, event_type: Type[DomainEvent], handler: Callable) -> None:
"""订阅异步事件处理器"""
if event_type not in self._async_handlers:
self._async_handlers[event_type] = []
self._async_handlers[event_type].append(handler)
print(f"[+] 订阅(异步): {handler.__name__} -> {event_type.__name__}")
def publish(self, event: DomainEvent) -> None:
"""
发布事件(同步)
依次调用所有订阅了该事件类型的处理器
"""
event_type = type(event)
handlers = self._handlers.get(event_type, [])
print(f"\n>>> 发布事件: {event_type.__name__}")
for handler in handlers:
try:
handler(event)
except Exception as e:
# 一个处理器失败不影响其他处理器
print(f"[x] 处理器 {handler.__name__} 执行失败: {e}")
async def publish_async(self, event: DomainEvent) -> None:
"""
发布事件(异步)
并发执行所有处理器
"""
event_type = type(event)
handlers = self._async_handlers.get(event_type, [])
print(f"\n>>> 发布事件(异步): {event_type.__name__}")
tasks = []
for handler in handlers:
if asyncio.iscoroutinefunction(handler):
tasks.append(handler(event))
else:
# 同步函数转异步执行
tasks.append(asyncio.to_thread(handler, event))
# 并发执行,收集异常但不中断
results = await asyncio.gather(*tasks, return_exceptions=True)
for handler, result in zip(handlers, results):
if isinstance(result, Exception):
print(f"[x] 处理器 {handler.__name__} 执行失败: {result}")
def clear(self) -> None:
"""清空所有订阅(测试时有用)"""
self._handlers.clear()
self._async_handlers.clear()
3.3 用装饰器简化订阅
每次都要调用 subscribe 方法有点麻烦,用装饰器更优雅:
python
# 全局事件总线实例
event_bus = EventBus()
def on_event(event_type: Type[DomainEvent]):
"""
事件订阅装饰器
用法:
@on_event(OrderCreatedEvent)
def handle_order_created(event):
...
"""
def decorator(handler: Callable):
event_bus.subscribe(event_type, handler)
return handler
return decorator
def on_event_async(event_type: Type[DomainEvent]):
"""异步事件订阅装饰器"""
def decorator(handler: Callable):
event_bus.subscribe_async(event_type, handler)
return handler
return decorator
3.4 定义处理器
python
# ========== 订单创建的处理器 ==========
@on_event(OrderCreatedEvent)
def send_order_confirmation_email(event: OrderCreatedEvent):
"""发送订单确认邮件"""
print(f" [邮件] 发送订单确认邮件: 订单#{event.order_id}")
@on_event(OrderCreatedEvent)
def decrease_inventory(event: OrderCreatedEvent):
"""扣减库存"""
print(f" [库存] 扣减库存: {len(event.items)} 件商品")
@on_event(OrderCreatedEvent)
def record_order_analytics(event: OrderCreatedEvent):
"""记录订单统计"""
print(f" [统计] 记录订单: 金额 {event.amount} 元")
# ========== 订单支付的处理器 ==========
@on_event(OrderPaidEvent)
def send_payment_notification(event: OrderPaidEvent):
"""发送支付成功通知"""
print(f" [支付] 支付成功通知: 订单#{event.order_id}, 金额 {event.amount} 元")
@on_event(OrderPaidEvent)
def add_user_points(event: OrderPaidEvent):
"""增加用户积分"""
points = int(event.amount / 10)
print(f" [积分] 增加积分: {points} 分")
# ========== 订单取消的处理器 ==========
@on_event(OrderCancelledEvent)
def restore_inventory(event: OrderCancelledEvent):
"""恢复库存"""
print(f" [库存] 恢复库存: 订单#{event.order_id}")
@on_event(OrderCancelledEvent)
def send_cancel_notification(event: OrderCancelledEvent):
"""发送取消通知"""
print(f" [邮件] 发送取消通知: 订单#{event.order_id}, 原因: {event.reason}")
3.5 在服务中使用
python
class OrderService:
"""订单服务:只关注核心业务,后续操作交给事件处理器"""
def __init__(self, order_repo):
self.order_repo = order_repo
def create_order(self, user_id: int, items: list) -> dict:
# 1. 核心业务逻辑:创建订单
order = {
"id": 1001,
"user_id": user_id,
"items": items,
"amount": sum(item["price"] * item["qty"] for item in items),
"status": "created"
}
self.order_repo.add(order)
# 2. 发布事件,不关心后续操作
event_bus.publish(OrderCreatedEvent(
order_id=order["id"],
user_id=user_id,
amount=order["amount"],
items=items
))
return order
def pay_order(self, order_id: int, payment_id: str) -> dict:
order = self.order_repo.get_by_id(order_id)
order["status"] = "paid"
self.order_repo.update(order)
event_bus.publish(OrderPaidEvent(
order_id=order_id,
payment_id=payment_id,
amount=order["amount"]
))
return order
def cancel_order(self, order_id: int, reason: str) -> dict:
order = self.order_repo.get_by_id(order_id)
order["status"] = "cancelled"
self.order_repo.update(order)
event_bus.publish(OrderCancelledEvent(
order_id=order_id,
reason=reason
))
return order
# ========== 测试 ==========
class FakeOrderRepo:
"""模拟仓储"""
def __init__(self):
self.orders = {}
def add(self, order):
self.orders[order["id"]] = order
def get_by_id(self, id):
return self.orders.get(id)
def update(self, order):
self.orders[order["id"]] = order
# 运行
service = OrderService(FakeOrderRepo())
print("=" * 50)
print("创建订单")
print("=" * 50)
service.create_order(
user_id=123,
items=[
{"name": "iPhone 15", "price": 7999, "qty": 1},
{"name": "手机壳", "price": 99, "qty": 2}
]
)
print("\n" + "=" * 50)
print("支付订单")
print("=" * 50)
service.pay_order(order_id=1001, payment_id="PAY_123456")
输出:
[+] 订阅: send_order_confirmation_email -> OrderCreatedEvent
[+] 订阅: decrease_inventory -> OrderCreatedEvent
[+] 订阅: record_order_analytics -> OrderCreatedEvent
[+] 订阅: send_payment_notification -> OrderPaidEvent
[+] 订阅: add_user_points -> OrderPaidEvent
[+] 订阅: restore_inventory -> OrderCancelledEvent
[+] 订阅: send_cancel_notification -> OrderCancelledEvent
==================================================
创建订单
==================================================
>>> 发布事件: OrderCreatedEvent
[邮件] 发送订单确认邮件: 订单#1001
[库存] 扣减库存: 2 件商品
[统计] 记录订单: 金额 8197 元
==================================================
支付订单
==================================================
>>> 发布事件: OrderPaidEvent
[支付] 支付成功通知: 订单#1001, 金额 8197 元
[积分] 增加积分: 819 分
看到了吗?OrderService 变得非常简洁,它只做核心业务(创建订单、更新状态),后续操作完全解耦到事件处理器里。新增功能?写个新的处理器加上 @on_event 装饰器就行。
四、RabbitMQ + Celery分布式实现
上面的实现是进程内的,观察者和发布者在同一个进程里。在微服务架构下,我们需要跨进程、跨服务的事件通知,这就需要消息队列了。
4.1 为什么需要消息队列?
| 场景 | 进程内事件总线 | 消息队列 |
|---|---|---|
| 单体应用 | 够用 | 杀鸡用牛刀 |
| 微服务 | 跨不了进程 | 必须 |
| 高并发 | 可能拖垮主服务 | 削峰填谷 |
| 可靠性 | 进程挂了事件丢了 | 持久化、重试 |
| 扩展性 | 单机瓶颈 | 水平扩展 |
4.2 架构图
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ FastAPI │ │ RabbitMQ │ │ Celery │
│ (发布事件) │ ───> │ (消息队列) │ ───> │ (消费事件) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
email_worker inventory_worker notification_worker
(邮件队列) (库存队列) (通知队列)
RabbitMQ 是消息中间件,负责存储和路由消息。Celery 是任务队列框架,负责消费消息并执行任务。两者配合,就能实现可靠的异步事件处理。
4.3 项目结构
myapp/
├── main.py # FastAPI 应用
├── celery_app.py # Celery 配置
├── tasks/ # Celery 任务(观察者)
│ ├── __init__.py
│ ├── email_tasks.py
│ ├── inventory_tasks.py
│ └── notification_tasks.py
├── events/ # 事件定义和发布者
│ ├── __init__.py
│ └── order_events.py
└── services/
└── order_service.py
4.4 Celery 配置
python
# celery_app.py
from celery import Celery
# 创建 Celery 应用
celery_app = Celery(
"myapp",
broker="amqp://guest:guest@localhost:5672//", # RabbitMQ 连接
backend="redis://localhost:6379/0", # 结果存储(可选)
include=[ # 自动发现任务
"tasks.email_tasks",
"tasks.inventory_tasks",
"tasks.notification_tasks",
]
)
# 配置
celery_app.conf.update(
# 序列化
task_serializer="json",
accept_content=["json"],
result_serializer="json",
# 时区
timezone="Asia/Shanghai",
enable_utc=True,
# 任务路由:不同任务发到不同队列
task_routes={
"tasks.email_tasks.*": {"queue": "email"},
"tasks.inventory_tasks.*": {"queue": "inventory"},
"tasks.notification_tasks.*": {"queue": "notification"},
},
# 可靠性配置
task_acks_late=True, # 任务执行完才确认
task_reject_on_worker_lost=True, # worker 挂了重新入队
# 重试配置
task_default_retry_delay=60, # 重试间隔 60 秒
task_max_retries=3, # 最大重试 3 次
)
4.5 定义 Celery 任务(观察者)
python
# tasks/email_tasks.py
from celery_app import celery_app
import time
@celery_app.task(bind=True, max_retries=3)
def send_order_confirmation_email(self, order_data: dict):
"""
发送订单确认邮件
bind=True: 可以通过 self 访问任务实例
max_retries: 最大重试次数
"""
try:
order_id = order_data["order_id"]
user_email = order_data["user_email"]
print(f"[邮件] 开始发送订单确认邮件: 订单#{order_id} -> {user_email}")
# 模拟发送邮件(实际项目用 SMTP 或第三方服务)
time.sleep(1)
print(f"[邮件] 发送成功: 订单#{order_id}")
return {"status": "sent", "order_id": order_id}
except Exception as e:
print(f"[邮件] 发送失败: {e},准备重试...")
# 失败重试,指数退避
raise self.retry(exc=e, countdown=60 * (2 ** self.request.retries))
@celery_app.task(bind=True, max_retries=3)
def send_payment_success_email(self, payment_data: dict):
"""发送支付成功邮件"""
try:
order_id = payment_data["order_id"]
amount = payment_data["amount"]
print(f"[支付] 发送支付成功邮件: 订单#{order_id}, 金额 {amount} 元")
time.sleep(0.5)
return {"status": "sent", "order_id": order_id}
except Exception as e:
raise self.retry(exc=e, countdown=60)
python
# tasks/inventory_tasks.py
from celery_app import celery_app
@celery_app.task(bind=True, max_retries=3)
def decrease_stock(self, order_data: dict):
"""
扣减库存
这是关键操作,必须保证可靠性
"""
try:
order_id = order_data["order_id"]
items = order_data.get("items", [])
print(f"[库存] 开始扣减库存: 订单#{order_id}")
for item in items:
print(f" - {item['name']} x {item['quantity']}")
# 实际项目这里调用库存服务 API
print(f"[库存] 扣减成功: 订单#{order_id}")
return {"status": "decreased", "order_id": order_id}
except Exception as e:
print(f"[库存] 扣减失败: {e}")
raise self.retry(exc=e, countdown=30)
@celery_app.task
def restore_stock(order_data: dict):
"""恢复库存(订单取消时)"""
order_id = order_data["order_id"]
print(f"[库存] 恢复库存: 订单#{order_id}")
# 实际项目调用库存服务
return {"status": "restored", "order_id": order_id}
python
# tasks/notification_tasks.py
from celery_app import celery_app
@celery_app.task
def send_sms_notification(data: dict):
"""发送短信通知"""
phone = data["phone"]
message = data["message"]
print(f"[短信] 发送短信: {phone} - {message}")
# 实际项目调用短信服务 API
return {"status": "sent", "phone": phone}
@celery_app.task
def send_push_notification(data: dict):
"""发送APP推送"""
user_id = data["user_id"]
message = data["message"]
print(f"[推送] APP推送: 用户#{user_id} - {message}")
# 实际项目调用推送服务
return {"status": "sent", "user_id": user_id}
@celery_app.task
def record_analytics(event_data: dict):
"""记录数据分析事件"""
event_type = event_data["event_type"]
print(f"[统计] 记录分析事件: {event_type}")
# 实际项目写入数据仓库或发送到分析平台
return {"status": "recorded", "event_type": event_type}
@celery_app.task
def add_user_points(data: dict):
"""增加用户积分"""
user_id = data["user_id"]
points = data["points"]
print(f"[积分] 增加积分: 用户#{user_id} +{points} 分")
# 实际项目调用积分服务
return {"status": "added", "user_id": user_id, "points": points}
4.6 事件发布者
python
# events/order_events.py
from typing import List
from dataclasses import dataclass, asdict
from celery import group, chain
# 导入任务
from tasks.email_tasks import send_order_confirmation_email, send_payment_success_email
from tasks.inventory_tasks import decrease_stock, restore_stock
from tasks.notification_tasks import (
send_sms_notification,
send_push_notification,
record_analytics,
add_user_points
)
@dataclass
class OrderEventData:
"""订单事件数据"""
order_id: int
user_id: int
user_email: str
user_phone: str
amount: float
items: List[dict]
class OrderEventPublisher:
"""
订单事件发布者
将事件转换为 Celery 任务并发送到消息队列
"""
@staticmethod
def publish_order_created(data: OrderEventData):
"""
发布订单创建事件
使用 group 并行执行多个任务
"""
event_data = asdict(data)
event_data["items"] = [
{"name": item.get("name", ""), "quantity": item.get("qty", 0)}
for item in data.items
]
# 构建任务组:这些任务会并行执行
job = group(
# 发送确认邮件
send_order_confirmation_email.s(event_data),
# 扣减库存
decrease_stock.s(event_data),
# 发送短信
send_sms_notification.s({
"phone": data.user_phone,
"message": f"您的订单 {data.order_id} 已创建,金额 {data.amount} 元"
}),
# APP推送
send_push_notification.s({
"user_id": data.user_id,
"message": "订单创建成功"
}),
# 数据统计
record_analytics.s({
"event_type": "order_created",
"order_id": data.order_id,
"amount": data.amount
}),
)
# 异步执行
result = job.apply_async()
print(f">>> 事件已发布: order_created, 任务组ID: {result.id}")
return result
@staticmethod
def publish_order_paid(data: OrderEventData):
"""发布订单支付事件"""
event_data = asdict(data)
job = group(
# 发送支付成功邮件
send_payment_success_email.s(event_data),
# 发送短信
send_sms_notification.s({
"phone": data.user_phone,
"message": f"订单 {data.order_id} 支付成功,金额 {data.amount} 元"
}),
# 增加积分
add_user_points.s({
"user_id": data.user_id,
"points": int(data.amount / 10) # 10元1积分
}),
# 数据统计
record_analytics.s({
"event_type": "order_paid",
"order_id": data.order_id,
"amount": data.amount
}),
)
result = job.apply_async()
print(f">>> 事件已发布: order_paid, 任务组ID: {result.id}")
return result
@staticmethod
def publish_order_cancelled(data: OrderEventData, reason: str = ""):
"""
发布订单取消事件
使用 chain 串行执行:先恢复库存,再发通知
"""
event_data = asdict(data)
# 取消订单需要先恢复库存,再发通知(有依赖关系)
job = chain(
# 先恢复库存
restore_stock.s(event_data),
# 再发短信通知
send_sms_notification.s({
"phone": data.user_phone,
"message": f"订单 {data.order_id} 已取消"
}),
)
result = job.apply_async()
print(f">>> 事件已发布: order_cancelled")
return result
这里用到了 Celery 的两个重要概念:
| 概念 | 说明 | 适用场景 |
|---|---|---|
group |
并行执行多个任务 | 任务之间无依赖,如发邮件、发短信 |
chain |
串行执行多个任务 | 任务之间有依赖,如先扣库存再发通知 |
4.7 在服务中使用
python
# services/order_service.py
from events.order_events import OrderEventPublisher, OrderEventData
class OrderService:
"""订单服务"""
def __init__(self, order_repo, user_repo):
self.order_repo = order_repo
self.user_repo = user_repo
def create_order(self, user_id: int, items: list) -> dict:
# 1. 获取用户信息
user = self.user_repo.get_by_id(user_id)
# 2. 创建订单(核心业务逻辑)
order = {
"id": 1001,
"user_id": user_id,
"items": items,
"amount": sum(item["price"] * item["qty"] for item in items),
"status": "created"
}
self.order_repo.add(order)
# 3. 发布事件(异步处理后续操作)
OrderEventPublisher.publish_order_created(OrderEventData(
order_id=order["id"],
user_id=user_id,
user_email=user["email"],
user_phone=user["phone"],
amount=order["amount"],
items=items
))
# 4. 立即返回,不等待后续操作完成
return order
def pay_order(self, order_id: int) -> dict:
order = self.order_repo.get_by_id(order_id)
user = self.user_repo.get_by_id(order["user_id"])
# 更新订单状态
order["status"] = "paid"
self.order_repo.update(order)
# 发布支付事件
OrderEventPublisher.publish_order_paid(OrderEventData(
order_id=order_id,
user_id=order["user_id"],
user_email=user["email"],
user_phone=user["phone"],
amount=order["amount"],
items=order["items"]
))
return order
def cancel_order(self, order_id: int, reason: str = "") -> dict:
order = self.order_repo.get_by_id(order_id)
user = self.user_repo.get_by_id(order["user_id"])
order["status"] = "cancelled"
self.order_repo.update(order)
OrderEventPublisher.publish_order_cancelled(
OrderEventData(
order_id=order_id,
user_id=order["user_id"],
user_email=user["email"],
user_phone=user["phone"],
amount=order["amount"],
items=order["items"]
),
reason=reason
)
return order
五、FastAPI实战整合
5.1 完整的 FastAPI 应用
python
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
from celery_app import celery_app
from events.order_events import OrderEventPublisher, OrderEventData
app = FastAPI(title="订单服务 - 事件驱动示例")
# ========== 请求模型 ==========
class OrderItem(BaseModel):
"""订单商品"""
product_id: int
name: str
price: float
qty: int
class CreateOrderRequest(BaseModel):
"""创建订单请求"""
user_id: int
items: List[OrderItem]
class CancelOrderRequest(BaseModel):
"""取消订单请求"""
reason: str = ""
# ========== 模拟数据 ==========
fake_users = {
1: {"id": 1, "email": "user1@example.com", "phone": "13800138001"},
2: {"id": 2, "email": "user2@example.com", "phone": "13800138002"},
}
fake_orders = {}
order_counter = 1000
# ========== API 接口 ==========
@app.post("/orders", summary="创建订单")
def create_order(request: CreateOrderRequest):
"""
创建订单
会触发以下异步任务:
- 发送订单确认邮件
- 扣减库存
- 发送短信通知
- APP推送
- 数据统计
"""
global order_counter
# 检查用户
user = fake_users.get(request.user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 创建订单
order_counter += 1
order = {
"id": order_counter,
"user_id": request.user_id,
"items": [item.model_dump() for item in request.items],
"amount": sum(item.price * item.qty for item in request.items),
"status": "created"
}
fake_orders[order["id"]] = order
# 发布事件(异步)
result = OrderEventPublisher.publish_order_created(OrderEventData(
order_id=order["id"],
user_id=request.user_id,
user_email=user["email"],
user_phone=user["phone"],
amount=order["amount"],
items=order["items"]
))
return {
"message": "订单创建成功",
"order": order,
"task_group_id": result.id # 返回任务组ID,可用于查询状态
}
@app.post("/orders/{order_id}/pay", summary="支付订单")
def pay_order(order_id: int):
"""
支付订单
会触发以下异步任务:
- 发送支付成功邮件
- 发送短信通知
- 增加用户积分
- 数据统计
"""
order = fake_orders.get(order_id)
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
if order["status"] != "created":
raise HTTPException(status_code=400, detail=f"订单状态不正确: {order['status']}")
user = fake_users[order["user_id"]]
# 更新状态
order["status"] = "paid"
# 发布事件
result = OrderEventPublisher.publish_order_paid(OrderEventData(
order_id=order_id,
user_id=order["user_id"],
user_email=user["email"],
user_phone=user["phone"],
amount=order["amount"],
items=order["items"]
))
return {
"message": "支付成功",
"order": order,
"task_group_id": result.id
}
@app.post("/orders/{order_id}/cancel", summary="取消订单")
def cancel_order(order_id: int, request: CancelOrderRequest):
"""
取消订单
会触发以下异步任务(串行执行):
- 恢复库存
- 发送取消通知
"""
order = fake_orders.get(order_id)
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
if order["status"] not in ["created", "paid"]:
raise HTTPException(status_code=400, detail=f"订单状态不允许取消: {order['status']}")
user = fake_users[order["user_id"]]
order["status"] = "cancelled"
result = OrderEventPublisher.publish_order_cancelled(
OrderEventData(
order_id=order_id,
user_id=order["user_id"],
user_email=user["email"],
user_phone=user["phone"],
amount=order["amount"],
items=order["items"]
),
reason=request.reason
)
return {
"message": "订单已取消",
"order": order,
"task_chain_id": result.id
}
@app.get("/orders/{order_id}", summary="查询订单")
def get_order(order_id: int):
"""查询订单详情"""
order = fake_orders.get(order_id)
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
return order
@app.get("/tasks/{task_id}", summary="查询任务状态")
def get_task_status(task_id: str):
"""
查询异步任务状态
状态说明:
- PENDING: 等待执行
- STARTED: 正在执行
- SUCCESS: 执行成功
- FAILURE: 执行失败
- RETRY: 重试中
"""
result = celery_app.AsyncResult(task_id)
response = {
"task_id": task_id,
"status": result.status,
}
if result.ready():
response["result"] = result.result
if result.failed():
response["error"] = str(result.result)
return response
@app.get("/health", summary="健康检查")
def health_check():
"""服务健康检查"""
return {"status": "healthy"}
5.2 运行方式
bash
# 1. 启动 RabbitMQ(Docker)
docker run -d \
--name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:management
# 2. 启动 Redis(用于存储任务结果)
docker run -d \
--name redis \
-p 6379:6379 \
redis
# 3. 启动 Celery Worker(可以启动多个,处理不同队列)
# 邮件 worker
celery -A celery_app worker -Q email -l info --concurrency=2 -n email_worker@%h
# 库存 worker
celery -A celery_app worker -Q inventory -l info --concurrency=2 -n inventory_worker@%h
# 通知 worker(并发高一点)
celery -A celery_app worker -Q notification -l info --concurrency=4 -n notification_worker@%h
# 4. 启动 FastAPI
uvicorn main:app --reload --port 8000
5.3 测试
bash
# 创建订单
curl -X POST http://localhost:8000/orders \
-H "Content-Type: application/json" \
-d '{
"user_id": 1,
"items": [
{"product_id": 1, "name": "iPhone 15", "price": 7999, "qty": 1},
{"product_id": 2, "name": "手机壳", "price": 99, "qty": 2}
]
}'
# 响应
{
"message": "订单创建成功",
"order": {
"id": 1001,
"user_id": 1,
"items": [...],
"amount": 8197,
"status": "created"
},
"task_group_id": "abc123..."
}
# 查询任务状态
curl http://localhost:8000/tasks/abc123...
# 支付订单
curl -X POST http://localhost:8000/orders/1001/pay
# 取消订单
curl -X POST http://localhost:8000/orders/1001/cancel \
-H "Content-Type: application/json" \
-d '{"reason": "不想要了"}'
观察 Celery Worker 的日志,会看到各个任务被分发到不同队列并执行。
六、同步vs异步观察者对比
| 方面 | 同步观察者(进程内) | 异步观察者(消息队列) |
|---|---|---|
| 响应时间 | 慢(等待所有处理完成) | 快(立即返回) |
| 可靠性 | 一个失败全失败 | 各自独立,可重试 |
| 扩展性 | 受限于单进程 | 可水平扩展 Worker |
| 复杂度 | 低 | 高(需要维护消息队列) |
| 一致性 | 强一致性 | 最终一致性 |
| 运维成本 | 低 | 高(RabbitMQ、Redis、Celery) |
| 适用场景 | 简单应用、强一致性要求 | 微服务、高并发、复杂业务 |
| 调试难度 | 简单(同一进程) | 较难(跨进程、异步) |
选择建议:
┌─ 是 ─> 消息队列 + Celery
│
┌─────────────────┐ │
│ 是否微服务架构? │ ────┤
└─────────────────┘ │
└─ 否 ─┬─ 高并发? ─┬─ 是 ─> 消息队列
│ │
│ └─ 否 ─> 进程内事件总线
│
└─ 需要可靠重试? ─┬─ 是 ─> 消息队列
│
└─ 否 ─> 进程内事件总线
七、常见问题
7.1 事件丢失怎么办?
问题描述 :消息发送到队列后,Worker 还没处理完就挂了,消息丢失。
解决方案:
python
# celery_app.py 配置
celery_app.conf.update(
# 任务执行完成后才确认消息
task_acks_late=True,
# Worker 异常退出时,消息重新入队
task_reject_on_worker_lost=True,
# 启用消息持久化
task_publish_retry=True,
task_publish_retry_policy={
"max_retries": 3,
"interval_start": 0,
"interval_step": 0.2,
"interval_max": 0.5,
},
)
7.2 任务执行失败如何重试?
问题描述:发邮件失败、调用外部 API 超时等情况。
解决方案:
python
@celery_app.task(
bind=True,
max_retries=3,
default_retry_delay=60,
autoretry_for=(Exception,), # 自动重试所有异常
retry_backoff=True, # 指数退避
retry_backoff_max=600, # 最大间隔 10 分钟
retry_jitter=True # 随机抖动,防止雪崩
)
def send_email(self, data: dict):
try:
# 发送邮件
...
except SMTPException as e:
# 手动重试,指定间隔
raise self.retry(exc=e, countdown=120)
7.3 如何保证事件处理顺序?
问题描述:订单创建事件必须在支付事件之前处理。
解决方案:
- 业务层面:通过状态机保证,不符合状态的事件直接拒绝
- 技术层面:同一订单的事件发到同一队列分区
python
# 使用 routing_key 保证同一订单的消息到同一 Worker
@celery_app.task
def process_order_event(event_data: dict):
pass
# 发送时指定 routing_key
process_order_event.apply_async(
args=[event_data],
routing_key=f"order.{order_id}" # 同一订单 ID 到同一分区
)
7.4 如何监控任务执行情况?
问题描述:想知道任务执行成功率、失败率、平均耗时。
解决方案 :使用 Flower 监控工具
bash
# 安装
pip install flower
# 启动监控面板
celery -A celery_app flower --port=5555
# 访问 http://localhost:5555
Flower 提供:
- 实时任务状态
- Worker 状态监控
- 任务执行统计
- 失败任务查看和重试
7.5 事件处理幂等性
问题描述:同一事件被处理多次(重试、重复投递)。
解决方案:
python
import redis
redis_client = redis.Redis()
@celery_app.task(bind=True)
def send_order_email(self, order_data: dict):
order_id = order_data["order_id"]
task_key = f"email:order:{order_id}"
# 检查是否已处理
if redis_client.get(task_key):
print(f"邮件已发送,跳过: 订单#{order_id}")
return {"status": "skipped", "reason": "already_sent"}
# 发送邮件
...
# 标记已处理(设置过期时间,比如 24 小时)
redis_client.setex(task_key, 86400, "1")
return {"status": "sent"}
八、总结
观察者模式的核心是解耦事件的发布和处理。发布者不需要知道有哪些处理者,处理者也不需要知道事件是谁发的。
本文介绍了三种实现方式:
| 实现方式 | 复杂度 | 适用场景 |
|---|---|---|
| 经典观察者模式 | 低 | 简单场景,学习理解 |
| 事件总线 | 中 | 单体应用,模块解耦 |
RabbitMQ + Celery |
高 | 微服务,高并发,需要可靠性 |
关键收益:
- 松耦合:新增后续操作不用改原有代码
- 可扩展:各处理逻辑独立,互不影响
- 高性能:异步执行,提升响应速度
- 高可靠:消息持久化,失败可重试
和其他模式的配合:
工厂模式:创建事件处理器依赖注入:配置事件总线仓储模式:处理数据持久化策略模式:不同事件类型的处理策略
这些模式组合起来,就是一个完整的事件驱动架构。
热门专栏推荐
- Agent小册
- 服务器部署
- Java基础合集
- Python基础合集
- Go基础合集
- 大数据合集
- 前端小册
- 数据库合集
- Redis合集
- Spring全家桶
- 微服务全家桶
- 数据结构与算法合集
- 设计模式小册
- 消息队列合集
等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟