【设计模式】Python观察者模式:用RabbitMQ+Celery实现事件驱动

Python观察者模式:用RabbitMQ+Celery实现事件驱动

前言

这篇是设计模式小册系列的学习笔记,这次整理的是观察者模式以及如何用 RabbitMQ + Celery 实现分布式事件驱动架构。

你可能天天在用观察者模式但没意识到------用户注册后发邮件、订单创建后扣库存、支付成功后发通知,这些"一个动作触发多个后续操作"的场景,本质上都是观察者模式的应用。

观察者模式的核心思想是解耦:触发事件的代码不需要知道有哪些后续操作,它只管发出"事件",至于谁来处理、怎么处理,它不关心。听起来有点像订阅 UP 主?没错,就是这个道理。

这篇文章会从最简单的 Python 实现开始,然后引入 RabbitMQ + Celery,展示如何在分布式环境下实现事件驱动架构。

🏠个人主页:山沐与山


文章目录


一、观察者模式基础

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
复杂度 高(需要维护消息队列)
一致性 强一致性 最终一致性
运维成本 高(RabbitMQRedisCelery
适用场景 简单应用、强一致性要求 微服务、高并发、复杂业务
调试难度 简单(同一进程) 较难(跨进程、异步)

选择建议:

复制代码
                        ┌─ 是 ─> 消息队列 + 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 如何保证事件处理顺序?

问题描述:订单创建事件必须在支付事件之前处理。

解决方案

  1. 业务层面:通过状态机保证,不符合状态的事件直接拒绝
  2. 技术层面:同一订单的事件发到同一队列分区
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 微服务,高并发,需要可靠性

关键收益

  1. 松耦合:新增后续操作不用改原有代码
  2. 可扩展:各处理逻辑独立,互不影响
  3. 高性能:异步执行,提升响应速度
  4. 高可靠:消息持久化,失败可重试

和其他模式的配合

  • 工厂模式:创建事件处理器
  • 依赖注入:配置事件总线
  • 仓储模式:处理数据持久化
  • 策略模式:不同事件类型的处理策略

这些模式组合起来,就是一个完整的事件驱动架构。


热门专栏推荐

等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊

希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏

如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟

相关推荐
硅星企鹅2 小时前
如何使用低代码爬虫工具采集复杂网页数据?
爬虫·python·低代码
GoKu~2 小时前
DDD设计模式例子
设计模式
superman超哥2 小时前
仓颉协程调度机制深度解析:高并发的秘密武器
c语言·开发语言·c++·python·仓颉
走向IT2 小时前
Python批量修改linux 密码脚本
linux·运维·服务器·python·批量·修改密码
倔强的小石头_2 小时前
Python 从入门到实战(十四):Flask 用户认证(给 Web 应用加安全锁,区分管理员与普通用户)
前端·python·flask
兴趣使然黄小黄2 小时前
【Pytest】使用Allure生成企业级测试报告
python·pytest
Wang's Blog2 小时前
RabbitMQ: 构建高可靠消息系统之定时重发、消费重试与死信告警全解析
分布式·rabbitmq
010不二2 小时前
基于Appium爬虫文本导出可话个人动态
数据库·爬虫·python·appium
TTGGGFF2 小时前
实用代码工具:Python打造PDF选区OCR / 截图批量处理工具(支持手动/全自动模式)
python·pdf·ocr