消息总线设计:asyncio.Queue实战

消息总线设计:asyncio.Queue实战

搞了两天,终于把 nanobot 的消息总线重构完了。说实话,一开始觉得这玩意儿挺简单的,不就是个队列吗?结果踩了一堆坑,记录一下。

为什么需要消息总线?

nanobot 是一个多渠道 AI 助手,要同时支持 Telegram、Discord、飞书、WhatsApp 等 20 多个平台。每个平台发来的消息都要:

  1. 解析格式
  2. 调用 LLM 生成回复
  3. 把回复发回对应平台

如果用同步的方式,一个平台卡住,其他平台都得等着。用户体验极差。

解决方案:消息总线。每个渠道独立收发消息,通过队列异步处理。

asyncio.Queue 基础

Python 的 asyncio.Queue 是协程安全的队列,非常适合做消息总线。

python 复制代码
import asyncio

# 创建队列
message_queue = asyncio.Queue()

# 生产者:往队列放消息
async def send_message(msg):
    await message_queue.put(msg)

# 消费者:从队列取消息
async def process_messages():
    while True:
        msg = await message_queue.get()
        # 处理消息
        await handle_message(msg)
        message_queue.task_done()

看起来很简单对吧?但实际用起来有几个坑。

坑一:队列满了怎么办?

asyncio.Queue(maxsize=N) 可以设置队列大小。但问题是:队列满了会阻塞

python 复制代码
# 这行代码会一直阻塞,直到队列有空位
await queue.put(msg)

如果消费者挂了,生产者就会卡死。解决方案是用 put_nowait() + 异常处理:

python 复制代码
try:
    queue.put_nowait(msg)
except asyncio.QueueFull:
    # 队列满了,记录日志,丢弃消息或重试
    logger.warning(f"Queue full, dropping message: {msg}")

坑二:消费者异常退出

消费者是个 while True 循环,如果里面抛了异常,整个消费者就停了。队列里的消息没人处理,越积越多。

解决方案 :用 try-except 包住整个循环,异常时记录日志并继续:

python 复制代码
async def process_messages():
    while True:
        try:
            msg = await queue.get()
            await handle_message(msg)
            queue.task_done()
        except Exception as e:
            logger.error(f"Error processing message: {e}")
            # 继续处理下一条

更好的做法是用 asyncio.Task 包装,异常时自动重启:

python 复制代码
async def run_consumer():
    while True:
        try:
            await process_messages()
        except Exception as e:
            logger.error(f"Consumer crashed: {e}, restarting...")
            await asyncio.sleep(1)  # 避免疯狂重启

坑三:多消费者竞争

nanobot 需要处理多种类型的消息:文本、图片、语音、文件等。如果只用一个队列,所有消息混在一起,处理逻辑会很乱。

解决方案:多队列 + 多消费者。

python 复制代码
# 按消息类型分队列
text_queue = asyncio.Queue()
media_queue = asyncio.Queue()
voice_queue = asyncio.Queue()

# 每个队列一个消费者
asyncio.create_task(process_text(text_queue))
asyncio.create_task(process_media(media_queue))
asyncio.create_task(process_voice(voice_queue))

# 路由消息
async def route_message(msg):
    if msg.type == "text":
        await text_queue.put(msg)
    elif msg.type in ["image", "video", "file"]:
        await media_queue.put(msg)
    elif msg.type == "voice":
        await voice_queue.put(msg)

这样每种消息类型有独立的处理流程,互不干扰。

完整的消息总线架构

nanobot 最终的架构是这样的:

python 复制代码
class MessageBus:
    def __init__(self):
        self.queues = {}  # channel_id -> Queue
        self.agent_loop = None
    
    async def start(self):
        """启动消息总线"""
        self.agent_loop = asyncio.create_task(self._agent_loop())
    
    async def receive(self, channel_id: str, message: dict):
        """接收来自渠道的消息"""
        if channel_id not in self.queues:
            self.queues[channel_id] = asyncio.Queue()
        
        await self.queues[channel_id].put(message)
    
    async def _agent_loop(self):
        """Agent 主循环:处理所有队列的消息"""
        while True:
            for channel_id, queue in self.queues.items():
                if not queue.empty():
                    msg = await queue.get()
                    # 调用 LLM 生成回复
                    reply = await self.call_llm(msg)
                    # 发回对应渠道
                    await self.send_to_channel(channel_id, reply)
            
            await asyncio.sleep(0.01)  # 避免 CPU 100%

这个架构的好处:

  1. 每个渠道独立队列:一个渠道慢了不影响其他渠道
  2. 统一 Agent 循环:所有消息走同一个 LLM 调用逻辑
  3. 易于扩展:新增渠道只需注册一个队列

性能优化:优先级队列

有些消息需要优先处理,比如用户取消请求。这时候可以用 asyncio.PriorityQueue

python 复制代码
from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedMessage:
    priority: int
    message: Any = field(compare=False)

# priority 越小越优先
queue = asyncio.PriorityQueue()
await queue.put(PrioritizedMessage(priority=1, message=cancel_request))
await queue.put(PrioritizedMessage(priority=10, message=normal_message))

写在最后

消息总线看起来简单,但要做好容错、扩展性、性能,还是有不少细节要考虑的。

nanobot 用这套架构跑了快一年了,支持 20+ 平台,每天处理几千条消息,还算稳定。

如果你也在做多渠道接入,可以参考这个设计。有问题欢迎评论区交流。


相关文章

相关推荐
伏 念2 小时前
大模型技术之LLM
人工智能·笔记·python·aigc
dapeng28702 小时前
使用Fabric自动化你的部署流程
jvm·数据库·python
站大爷IP2 小时前
使用 Python 实现 Word 文档文本格式化全解析
python
AI-小柒2 小时前
DataEyes 聚合平台 + Claude Code Max 编程实战
android·开发语言·人工智能·windows·python·macos·adb
SUBMAIL赛邮云通信2 小时前
如何用Python调用SUBMAIL SMS API发送国际短信
python
2401_874732532 小时前
构建一个桌面版的天气预报应用
jvm·数据库·python
qq_417695053 小时前
实战:用Python开发一个简单的区块链
jvm·数据库·python
我的xiaodoujiao3 小时前
3、API 接口自动化测试详细图文教程学习系列3--相关Python基础知识2
python·学习·测试工具·pytest
南 阳3 小时前
Python从入门到精通day56
开发语言·python