系统设计:架构与接口设计
设计分层架构、数据模型、接口和失败边界
生命周期 · 阶段三 系统设计 | 上游:需求与选型结论(02)| 下游:开发实现(04~07)
本文站在系统设计者(架构师)角度,给出一套部门级 RAG 的参考架构、数据模型、接口和时序。它是设计起点,不是可以原样套用的生产规范;容量、合规、可用性和权限要求应由项目需求决定。
一、分层架构设计
flowchart TB
A["接入层 | Nginx 网关
HTTPS · 鉴权 · 限流 · SSE 透传"]
B["应用层 | FastAPI 服务(无状态,可横向扩容)
文档管理 · 问答服务 · 知识库管理 · 反馈统计 · 权限对接"]
C["异步层 | Celery Worker ×N + Redis 队列
接收 → 解析 → OCR → 切片 → 向量化 → 索引"]
D["编排层 | 查询链路
改写 → 混合检索 → RRF → 重排 → Prompt → 生成"]
E["模型层(OpenAI 兼容协议,可插拔)
Embedding(TEI) · Rerank · LLM(vLLM/Ollama/API)"]
F["存储层
MySQL(元数据) · Qdrant/Milvus(向量) · MinIO(原件) · Redis(队列/缓存) · ES(可选,BM25)"]
A --> B
B --> C
B --> D
C --> E
D --> E
C --> F
D --> F
style A fill:#f5f5f5,stroke:#999
style E fill:#fff0f5,stroke:#e06fa8
style F fill:#f0fff4,stroke:#4abf60
设计原则:
- 应用层无状态 → 可任意扩容;重活(入库)全部下沉异步层
- 模型层独立部署、接口标准化(OpenAI 兼容协议),模型可插拔
- 存储分工明确:关系数据、向量、文件各归其位,不混用
二、模块职责与失败影响
| 模块 | 职责 | 失败影响 | 可用性要求 |
|---|---|---|---|
| 网关 | 统一入口、TLS、限流 | 全站不可用 | 主备/多活 |
| 应用服务 | 业务 API、编排查询链路 | 问答不可用 | 多副本 |
| 入库 Worker | 执行流水线任务 | 新文档无法入库,存量问答不受影响 | 可降级 |
| 模型服务 | Embedding/Rerank/LLM | 问答不可用(可降级纯检索) | 多实例 |
| 向量库 | 切片存储检索 | 问答不可用 | 副本/持久卷 |
| MySQL | 元数据、任务状态 | 管理功能异常 | 主从 |
提示 设计要点
入库链路与问答链路解耦。入库挂了不影响已有知识的服务,这是架构可用性的关键分割线。
三、数据模型设计(MySQL)
-- 知识库表(多知识库/多租户基础)
CREATE TABLE kb_knowledge_base (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL COMMENT '知识库名称',
owner_dept VARCHAR(64) COMMENT '归属部门',
embed_model VARCHAR(64) NOT NULL COMMENT '向量模型(锁定,换模型须重建)',
chunk_size INT DEFAULT 500,
chunk_overlap INT DEFAULT 50,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
is_deleted TINYINT DEFAULT 0,
INDEX idx_dept (owner_dept)
);
-- 文档表
CREATE TABLE kb_document (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
kb_id BIGINT NOT NULL,
doc_name VARCHAR(256) NOT NULL,
file_hash CHAR(64) NOT NULL COMMENT 'SHA256,去重依据',
file_type VARCHAR(16),
file_size BIGINT,
storage_path VARCHAR(512) COMMENT 'MinIO 路径',
version INT DEFAULT 1,
status VARCHAR(16) DEFAULT 'PENDING' COMMENT '见任务状态机',
security VARCHAR(16) DEFAULT '内部' COMMENT '公开/内部/秘密',
uploader VARCHAR(64),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_hash_kb (kb_id, file_hash, version),
INDEX idx_kb_status (kb_id, status)
);
-- 入库任务表(流水线状态机载体)
CREATE TABLE kb_ingest_task (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
doc_id BIGINT NOT NULL,
stage VARCHAR(16) COMMENT 'PARSING/OCR/CHUNKING/EMBEDDING/INDEXING',
status VARCHAR(16) DEFAULT 'PENDING' COMMENT 'PENDING/RUNNING/SUCCESS/FAILED',
progress INT DEFAULT 0 COMMENT '0-100',
chunk_count INT DEFAULT 0,
error_msg TEXT,
retry_count INT DEFAULT 0,
started_at DATETIME,
finished_at DATETIME,
INDEX idx_doc (doc_id), INDEX idx_status (status)
);
-- 切片镜像表(可选:原文在向量库 payload,此表用于管理与对账)
CREATE TABLE kb_chunk (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
doc_id BIGINT NOT NULL,
chunk_index INT NOT NULL,
content MEDIUMTEXT,
token_count INT,
page INT,
section VARCHAR(256),
vector_id VARCHAR(64) COMMENT '向量库中对应 point id',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_doc_idx (doc_id, chunk_index)
);
-- 会话与反馈表
CREATE TABLE kb_conversation (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id VARCHAR(64), kb_id BIGINT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE kb_message (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
conv_id BIGINT, role VARCHAR(8), content TEXT,
retrieved_json JSON COMMENT '检索明细(排障用,见06篇)',
latency_ms INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_conv (conv_id)
);
CREATE TABLE kb_feedback (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
message_id BIGINT, user_id VARCHAR(64),
rating TINYINT COMMENT '1赞/-1踩',
comment VARCHAR(512),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_msg (message_id)
);
任务状态机(入库流水线的"进度条")
stateDiagram-v2
[*] --> PENDING
PENDING --> PARSING
PARSING --> OCR_STAGE: 扫描件/图片型
PARSING --> CHUNKING: 文本型直接进
OCR_STAGE --> CHUNKING
CHUNKING --> EMBEDDING
EMBEDDING --> INDEXING
INDEXING --> SUCCESS
PENDING --> FAILED
PARSING --> FAILED
OCR_STAGE --> FAILED
CHUNKING --> FAILED
EMBEDDING --> FAILED
INDEXING --> FAILED
FAILED --> PENDING: 重试续跑(从失败 stage 继续)
SUCCESS --> [*]
note right of FAILED
记录 stage 与 error_msg
end note
note right of SUCCESS
回写 chunk_count 与 finished_at
end note
四、向量库设计
多知识库隔离方案对比
| 方案 | 说明 | 适用 |
|---|---|---|
单 Collection + kb_id 过滤 | 简单,资源省;过滤有轻微性能损耗 | 知识库数 < 20,切片总量 < 500 万 |
| Partition 按 kb 划分 | Milvus partition,隔离较好 | 中大型,库数适中 |
| 每知识库一个 Collection | 物理隔离最彻底,管理成本高 | 租户强隔离要求(对外 SaaS) |
字段与索引设计
沿用 05 篇 Schema,补充设计考量:
vector_id生成规则:{doc_id}:{chunk_index}→ 天然幂等,重复入库直接覆盖- payload 冗余
doc_name/page/section:检索结果无需回表即可展示引用 department/security必须进 payload:权限过滤是检索层强制项
五、API 接口设计
接口清单
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| POST | /api/v1/kbs | 创建知识库 | 管理员 |
| POST | /api/v1/documents | 上传文档(multipart)→ 返回任务 ID | 库成员 |
| GET | /api/v1/documents/{id}/status | 查询入库进度 | 库成员 |
| GET | /api/v1/documents?page=1&size=20 | 文档列表(分页) | 库成员 |
| DELETE | /api/v1/documents/{id} | 删除文档(联动删切片) | 管理员 |
| POST | /api/v1/chat | 问答(SSE 流式) | 登录用户 |
| GET | /api/v1/chat/history/{conv_id} | 会话历史 | 本人 |
| POST | /api/v1/feedback | 点赞/点踩 | 登录用户 |
| GET | /api/v1/health | 健康检查(网关探活) | 匿名 |
核心接口示例
上传文档
POST /api/v1/documents?kb_id=1
Headers: Authorization: Bearer <jwt>
Content-Type: multipart/form-data
file=<二进制>
// 200
{
"code": 0,
"data": {
"doc_id": 1001,
"task_id": 88,
"status": "PENDING",
"deduplicated": false
}
}
问答(SSE)
POST /api/v1/chat
{ "kb_id": 1, "conv_id": 3, "question": "抵押物处置流程是什么?" }
data: {"type":"meta","retrieved":[{"doc":"处置手册","page":12,"score":0.87}]}
data: {"type":"delta","content":"根据"}
data: {"type":"delta","content":"《处置操作手册》第12页"}
data: {"type":"done","message_id":5001,"latency_ms":2310}
data: {"type":"refuse","reason":"知识库中未找到相关内容"}
错误码规范
| 错误码 | 含义 |
|---|---|
| 0 | 成功 |
| 1001 | 参数校验失败 |
| 1002 | 不支持的文件格式 |
| 1003 | 文件超过大小限制 |
| 2001 | 无该知识库权限 |
| 2002 | 文档已存在(重复上传) |
| 3001 | 入库任务失败(附 stage) |
| 5001 | 模型服务不可用/降级中 |
设计规范
- URL 版本化(
/api/v1),破坏性变更升 v2 - 上传接口支持
Idempotency-Key头 + 文件哈希双重防重 - 分页统一
page/size,响应统一{code, message, data}包装 - SSE 响应禁止缓冲(网关配置见 10 篇)
六、核心时序图
入库时序
sequenceDiagram
autonumber
actor U as 用户
participant A as 应用服务
participant MY as MySQL
participant MO as MinIO
participant Q as Redis队列
participant W as Worker
participant E as 模型服务
participant V as 向量库
U->>A: 上传文档
A->>MO: 存原件
A->>MY: 建 doc + task(PENDING)
A->>Q: 发任务
A-->>U: 返回 task_id
Q->>W: 取任务
W->>MY: 解析/OCR/切片(状态逐段回写)
W->>E: 批量向量化
W->>V: upsert 向量 + payload
W->>MY: task = SUCCESS
U->>A: 轮询进度
A-->>U: 返回进度/最终状态
问答时序
sequenceDiagram
autonumber
actor U as 用户
participant A as 应用服务
participant RW as 改写(LLM/规则)
participant V as 向量库
participant ES as ES(BM25)
participant RR as Rerank
participant L as LLM
U->>A: 提问
A->>RW: 清洗/改写/补全
RW-->>A: 返回改写 query
par 双路并发
A->>V: 向量检索 top20
and
A->>ES: 关键词检索 top20
end
A->>A: RRF 融合 → 候选 30
A->>RR: 精排
RR-->>A: top5
A->>L: 组装 Prompt(指令+切片+问题)
L-->>A: SSE 流式 delta
A-->>U: 流式回答 + 引用
A->>A: 落库 message(检索明细/耗时)
七、关键设计决策记录
| # | 决策点 | 结论 | 理由 |
|---|---|---|---|
| 1 | 入库同步 or 异步 | 超过项目实测阈值后转异步任务 | 用解析耗时和网关超时实测确定阈值,示例可从 1MB 或 10 页开始验证 |
| 2 | 切片镜像表要不要 | 保留 kb_chunk | 对账、切片级管理、重建向量库时免重新解析 |
| 3 | 查询改写放哪 | 规则清洗默认开,LLM 改写可配置 | 省延迟,效果不足再开 LLM |
| 4 | 缓存策略 | 归一化 query 哈希 → 缓存答案 30min | 高频重复问题省 LLM 开销 |
| 5 | 模型接入 | OpenAI 兼容协议 + 适配器模式 | 一套代码切换 vLLM/Ollama/商用 API |
| 6 | 权限过滤位置 | 检索层 filter 强制执行 | Prompt 层约束可被注入绕过(红线) |
| 7 | 删除策略 | 软删文档 + 物理删切片 | 审计可回溯,检索即刻干净 |
八、非功能设计要求
- 高可用:应用 ≥2 副本无状态部署;模型服务多实例;向量库持久卷 + 副本;任一 Worker 宕机任务可被其他节点接管(任务表抢占机制)
- 性能指标写入设计:按真实业务定义问答 P95、入库时延和并发目标;例如先以 P95 8s、50 页入库 3min、50 并发作为待验证基线
- 可观测性内建:trace_id 从网关贯穿到 LLM 调用;检索明细全量落 kb_message
- 可测试性内建:模型层全部 Mock 注入点;评测集回归脚本随代码库维护(见 09 篇)
- 安全设计:JWT 鉴权、上传文件类型白名单 + 魔数校验、审计日志三件套(谁/何时/干了什么)
本篇交付与下一步
本阶段应交付架构图、数据模型、接口契约、权限过滤位置、失败降级和容量假设,并为每个数字注明来源或“待压测”。下一步进入 04-环境搭建与开发准备,用最小环境验证这些设计假设。