第二章 系统总体架构设计


优秀的系统不是一开始就设计好的,而是在不断解决真实问题的过程中逐渐演进出来的。

如果现在回头来看整个项目,很多人可能会觉得最终的架构十分自然:

企业微信
Webhook(FastAPI)
Redis 消息队列
Worker
DeepSeek
企业微信发送接口
用户收到回复

但事实上,我们最初并不是这样设计的。

整个架构经历了几轮迭代,每一次调整都来自真实运行过程中暴露出来的问题。本章将完整介绍系统的设计目标、架构演进过程,以及为什么最终形成现在这种解耦、异步、可扩展的实现方式。


2.1 从需求出发,而不是从代码出发

很多开发者在接到一个需求后,第一反应是打开编辑器开始写代码。

例如,当有人提出:

“做一个企业微信 AI 自动回复。”

很多人的第一想法就是:

  1. 接收企业微信消息。
  2. 调用大模型接口。
  3. 返回 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 的职责变得非常单纯:

  1. 验签。
  2. 解密消息。
  3. 去重。
  4. 写入 Redis。
  5. 立即返回 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 的代码。


本章小结

系统架构的设计,从来不是为了追求复杂,而是为了应对真实问题。

在本章中,我们经历了三次思路上的转变:

  1. 从“收到消息立即调用 AI”的同步模型,转向“接收与处理分离”的异步模型。
  2. 引入 Redis,将消息处理从 Webhook 中剥离出来,实现解耦。
  3. 使用 Worker 承担 AI 推理和消息发送,使整个系统更稳定、更容易扩展。

这些调整并不是预先规划好的,而是在实际部署、调试和排查问题的过程中逐步形成的。

正是这种从真实问题中不断演进架构的过程,才是工程实践最有价值的部分。


下一章预告

第三章《技术选型与核心组件》将详细介绍为什么最终选择:

  • Ubuntu + Docker 作为运行环境;
  • FastAPI 作为 Web 服务框架;
  • Redis 作为消息队列;
  • DeepSeek 作为大语言模型;
  • OpenResty 作为反向代理与 HTTPS 入口;

并分析每一种技术选择背后的原因,以及它们在整个系统中的职责和边界。