第二章 系统总体架构设计
优秀的系统不是一开始就设计好的,而是在不断解决真实问题的过程中逐渐演进出来的。
如果现在回头来看整个项目,很多人可能会觉得最终的架构十分自然:
企业微信
│
▼
Webhook(FastAPI)
│
▼
Redis 消息队列
│
▼
Worker
│
▼
DeepSeek
│
▼
企业微信发送接口
│
▼
用户收到回复
但事实上,我们最初并不是这样设计的。
整个架构经历了几轮迭代,每一次调整都来自真实运行过程中暴露出来的问题。本章将完整介绍系统的设计目标、架构演进过程,以及为什么最终形成现在这种解耦、异步、可扩展的实现方式。
2.1 从需求出发,而不是从代码出发
很多开发者在接到一个需求后,第一反应是打开编辑器开始写代码。
例如,当有人提出:
“做一个企业微信 AI 自动回复。”
很多人的第一想法就是:
- 接收企业微信消息。
- 调用大模型接口。
- 返回 AI 回复。
看似没有问题。
但是,真正开始分析业务后,我们会发现,这个需求远远不只是“自动回复”。
如果把需求拆开来看,它至少包含以下几个方面:
一、消息必须能够稳定接收
企业微信发送消息后,会主动回调我们提供的接口。
因此系统必须能够:
- 正确完成 URL 验证。
- 解密企业微信加密消息。
- 解析 XML 内容。
- 提取发送者、消息类型和消息内容。
- 快速响应企业微信服务器。
这里有一个非常重要的原则:
Webhook 的职责是“接收消息”,而不是“处理消息”。
这是后续整个架构设计的核心思想。
二、消息不能丢失
如果系统在调用 AI 时崩溃,用户消息怎么办?
如果服务器重启怎么办?
如果 DeepSeek 服务暂时不可用怎么办?
如果网络抖动导致发送失败怎么办?
如果直接在内存中处理消息,这些消息都会丢失。
因此,系统需要一种能够暂存消息、支持失败重试的机制。
这也是后来引入 Redis 消息队列的重要原因。
三、不能因为 AI 响应慢而导致回调失败
这是很多初学者最容易忽略的问题。
企业微信回调接口并不会无限等待。
Webhook 的职责是确认:
“消息我已经收到。”
而不是:
“我已经处理完成。”
如果把 AI 调用放在回调接口中,那么:
企业微信
│
▼
Webhook
│
▼
DeepSeek(等待几秒)
一旦模型响应时间超过企业微信允许的时间,就可能出现:
- 回调超时
- 企业微信重新推送消息
- 同一条消息重复处理
- 用户收到重复回复
这也是我们后来彻底放弃同步处理的重要原因。
四、未来必须支持更多能力
项目虽然从“自动回复”开始,但目标绝不仅仅是聊天。
我们希望未来能够逐步扩展,例如:
- 企业知识库问答(RAG)。
- 客户意图识别。
- 销售线索评分。
- CRM 自动建档。
- 自动生成日报。
- 自动总结客户沟通记录。
- 多 Agent 协同处理。
因此,从第一天开始,系统就必须具备良好的扩展能力,而不能为了快速实现功能,把所有逻辑都堆在一个文件中。
2.2 第一版架构:最直接的实现方式
几乎所有人第一次实现企业微信 AI 回复时,都会采用下面这种架构:
企业微信
│
▼
FastAPI
│
▼
DeepSeek
│
▼
企业微信发送接口
这种方案的优点非常明显:
- 架构简单。
- 开发速度快。
- 几十行代码即可完成。
- 非常适合学习企业微信接口。
但是,它也隐藏着很多问题。
同步架构的问题
假设 DeepSeek 平均响应时间为:
- 正常:2~5 秒
- 高峰期:5~15 秒
- 网络异常:更长
那么整个 Webhook 将一直等待。
此时:
Webhook
│
├── 等待 AI
├── 等待网络
├── 等待返回
整个接口都被阻塞。
如果同时有多个用户发送消息,系统很容易因为等待 AI 而积压请求。
这不仅影响性能,还可能触发企业微信的超时重试机制。
重复消息的问题
企业微信为了保证消息可靠送达,当它认为回调没有成功时,会重新推送同一条消息。
如果系统没有设计去重机制,就可能出现:
同一条消息
│
▼
AI 回复一次
企业微信再次推送
│
▼
AI 再回复一次
最终用户会收到两条甚至更多相同内容。
在我们实际调试过程中,就曾观察到企业微信在异常情况下多次回调同一条消息,因此后续增加了基于消息内容和请求参数生成哈希值的去重机制,并将结果缓存到 Redis 中,在一定时间内忽略重复请求。
2.3 第二版架构:引入 Redis,实现消息解耦
解决同步阻塞问题最直接的方法,就是把“接收消息”和“处理消息”分开。
于是,我们引入了 Redis 消息队列。
架构演变为:
企业微信
│
▼
Webhook
│
▼
Redis Queue
│
▼
Worker
│
▼
DeepSeek
Webhook 的职责变得非常单纯:
- 验签。
- 解密消息。
- 去重。
- 写入 Redis。
- 立即返回 success。
整个过程通常只需要几十毫秒。
这样一来,无论 AI 需要几秒钟完成推理,都不会影响企业微信回调。
2.4 为什么选择 Redis,而不是数据库
理论上,我们完全可以把消息保存到数据库。
例如:
Webhook
│
▼
MySQL
然后由后台程序定时扫描数据库处理消息。
但数据库更适合持久化存储,而不是高频消息分发。
Redis 更适合作为消息缓冲层,因为它具有以下优势:
- 内存存储,读写速度快。
- 支持阻塞队列,Worker 无需轮询数据库。
- 配置简单,部署方便。
- 后续可升级到 Redis Stream,而无需推翻整体架构。
在我们的项目中,Webhook 只负责把消息压入 Redis,Worker 则持续监听队列并处理任务。这种模式既提高了吞吐能力,也使系统具备了天然的解耦特性。
2.5 Worker:让 AI 成为后台能力
引入 Worker 后,AI 不再运行在 Webhook 中,而是在后台独立处理。
这样,整个系统可以划分为两个完全不同的职责:
Webhook:
- 接收消息。
- 验签。
- 解密。
- 去重。
- 入队。
Worker:
- 从 Redis 获取任务。
- 调用 DeepSeek。
- 生成回复。
- 调用企业微信发送接口。
- 记录日志。
这种职责划分看似简单,却带来了几个重要优势:
第一,Webhook 不再依赖 AI 服务的响应速度。
第二,即使 AI 服务短暂不可用,消息仍然保留在队列中,不会立即丢失。
第三,未来可以根据业务量增加多个 Worker,同时消费同一个队列,提高整体处理能力,而无需修改 Webhook 的代码。
本章小结
系统架构的设计,从来不是为了追求复杂,而是为了应对真实问题。
在本章中,我们经历了三次思路上的转变:
- 从“收到消息立即调用 AI”的同步模型,转向“接收与处理分离”的异步模型。
- 引入 Redis,将消息处理从 Webhook 中剥离出来,实现解耦。
- 使用 Worker 承担 AI 推理和消息发送,使整个系统更稳定、更容易扩展。
这些调整并不是预先规划好的,而是在实际部署、调试和排查问题的过程中逐步形成的。
正是这种从真实问题中不断演进架构的过程,才是工程实践最有价值的部分。
下一章预告
第三章《技术选型与核心组件》将详细介绍为什么最终选择:
- Ubuntu + Docker 作为运行环境;
- FastAPI 作为 Web 服务框架;
- Redis 作为消息队列;
- DeepSeek 作为大语言模型;
- OpenResty 作为反向代理与 HTTPS 入口;
并分析每一种技术选择背后的原因,以及它们在整个系统中的职责和边界。