[!NOTE] 输入输出、节点流转、按需求拆分任务、多Agent框架、Sub-Agent 职责划分
多 Agent 协作职责划分
整体协作图
graph TD
O["Orchestrator (Router)<br/>意图分类 / 任务分发 / 结果聚合"]
O --> J["JD Worker Pool (1-2)"]
O --> R["Resume Worker Pool (5-10)"]
O --> M["Match Worker Pool (3-5)"]
J --> JS["JDSchema"]
R --> RS["ResumeSchema"]
M --> MR["MatchResult"]
1. Orchestrator (Router)
| 项目 | 内容 |
|---|---|
| 类型 | 调度者(不是执行者) |
| 模型 | Haiku 4.5(简单路由,便宜) |
| 职责 | 意图分类、任务分发、结果聚合 |
输入:
HR: “筛选 5 年以上 Python 工程师,重点看架构能力”
Orchestrator Think:
- 意图识别:这是一个「筛选请求」
- 需要 JD 解析 + 批量简历匹配
- 分发 JD Worker → 等待 JDSchema
- 分发 Resume Worker → 等待批量 ResumeSchema
- 分发 Match Worker → 等待 MatchResults
- 聚合结果返回给 HR
输出:
{
"intent": "screen_candidates",
"tasks": [
{"task_id": "t1", "agent": "JDWorker", "input": "..."},
{"task_id": "t2", "agent": "ResumeWorker", "input": "resume_batch_001"},
{"task_id": "t3", "agent": "MatchWorker", "input": {"jd: "t1", "resumes": "t2"}}
],
"wait_for": ["t3"]
}
2. JD Worker
| 项目 | 内容 |
|---|---|
| 类型 | 单任务执行者 |
| 模型 | Sonnet 4.6(深度理解 JD) |
| 副本 | 1-2 个(JD 解析不频繁) |
职责:将 JD 文本解析为结构化 JDSchema
输入:
“招聘高级 Python 后端开发,要求 5 年以上经验,熟悉 FastAPI、Django,有微服务架构经验优先,具备良好的团队协作能力和沟通能力,本科及以上学历”
处理流程:
- LLM 提取关键实体(技能、经验、学历)
- 构建技能树 + 权重
- 区分 required vs optional
- 异常检测(技能矛盾、要求不合理)
- 输出 JDSchema
输出:
{
"job_id": "jd_001",
"job_title": "高级 Python 后端开发",
"硬技能": [
{"skill": "Python", "weight": 0.3, "required": true, "level": "expert"},
{"skill": "FastAPI", "weight": 0.2, "required": true, "level": "advanced"},
{"skill": "Django", "weight": 0.15, "required": true, "level": "advanced"},
{"skill": "微服务架构", "weight": 0.1, "required": false, "level": "intermediate"}
],
"软技能": [
{"skill": "团队协作", "weight": 0.15, "required": true},
{"skill": "沟通能力", "weight": 0.1, "required": true}
],
"经验年限": {"min": 5},
"学历": {"min": "本科"},
"jd_embedding": [0.123, ...]
}
3. Resume Worker
| 项目 | 内容 |
|---|---|
| 类型 | 单任务执行者 |
| 模型 | Haiku 4.5(高吞吐,便宜) |
| 副本 | 5-10 个(简历量大) |
| 特点 | 无状态,可水平扩展 |
职责:解析简历为结构化 ResumeSchema
输入:
resume_file: PDF/DOCX/IMG
candidate_id: “cand_001”
处理流程:
graph TD
A["1. 格式检测 (PDF/DOCX/HTML/IMG)"]
B["2. 文本提取<br/>- PDF: pdf-parse<br/>- DOCX: python-docx<br/>- IMG: Tesseract OCR"]
C["3. LLM 结构化提取<br/>- 姓名、联系方式<br/>- 工作经历<br/>- 教育背景<br/>- 技能清单"]
D["4. 向量化(用于向量召回)"]
E["5. 输出 ResumeSchema"]
A --> B --> C --> D --> E
输出:
{
"candidate_id": "cand_001",
"name": "张三",
"工作经历": [
{
"company": "XX科技",
"title": "高级工程师",
"duration": "2020.03 - 至今",
"years": 4,
"description": "负责后端架构设计,使用 FastAPI 重构微服务...",
"skills_extracted": ["Python", "FastAPI", "微服务"]
}
],
"教育背景": {"degree": "本科", "major": "计算机"},
"技能清单": {
"Python": {"level": "expert", "years": 6},
"FastAPI": {"level": "advanced", "years": 3}
},
"总工作经验": 6,
"resume_embedding": [0.456, ...]
}
4. Match Worker
| 项目 | 内容 |
|---|---|
| 类型 | 单任务执行者 |
| 模型 | Sonnet 4.6(深度匹配+推理) |
| 副本 | 3-5 个 |
| 特点 | 核心计算模块,最耗时 |
职责:计算 JD 与简历的匹配度 + 生成解释
输入:
{
"jd": JDSchema,
"candidate": ResumeSchema
}
处理流程:
graph TD
A["1. 硬技能匹配 (40%)<br/>技能级别对比 / 技能是否存在"]
B["2. 软技能匹配 (20%)<br/>语义相似度计算 / 从工作描述推断软技能"]
C["3. 经验年限匹配 (25%)<br/>简历 6 年 ≥ JD 要求 5 年"]
D["4. 学历匹配 (15%)<br/>简历本科 ≥ JD 要求本科"]
E["5. 优选条件加分 (5%)<br/>简历有微服务经验 +20% 加分"]
F["6. 综合得分计算<br/>36.8 + 15.6 + 25.0 + 15.0 + 1.0 = 85.6 分"]
G["7. Explanation 生成<br/>为什么推荐/不推荐、优势、潜在风险、面试重点"]
A --> B --> C --> D --> E --> F --> G
输出:
{
"candidate_id": "cand_001",
"total_score": 85.6,
"recommendation": "强烈推荐",
"breakdown": {
"硬技能": {"score": 92, "weighted": 36.8},
"软技能": {"score": 78, "weighted": 15.6},
"经验": {"score": 100, "weighted": 25.0},
"学历": {"score": 100, "weighted": 15.0},
"优选条件": {"score": 20, "weighted": 1.0}
},
"explanation": {
"summary": "综合得分 85.6,与岗位高度匹配",
"strengths": [
"Python 专家级别(6年),超过岗位要求",
"FastAPI/Django 技能与岗位吻合",
"具备微服务架构经验(优选条件加分)"
],
"concerns": ["软技能建议面试核实"],
"missing": [],
"interview_focus": ["微服务架构设计思路", "团队协作角色"]
},
"confidence": 0.89
}
5. Worker 间协作时序
graph TD
REQ["HR Request"]
O1["Orchestrator<br/>1. 意图分类:「筛选候选人」<br/>2. 任务分发"]
JD["JD Worker<br/>解析 JD"]
RR["Resume Repo (批量读取)"]
MW["Match Worker Pool<br/>Match #1 / #2 / #N<br/>并行计算每个候选人的匹配度"]
O2["Orchestrator<br/>聚合 + 排序 + 分页<br/>返回 Top-K 给 HR"]
REQ --> O1
O1 --> JD
O1 --> RR
JD -- "JDSchema" --> MW
RR -- "ResumeSchema[]" --> MW
MW -- "MatchResults[]" --> O2
6. 职责边界清晰化
| 问题 | 解答 |
|---|---|
| JD Worker 太多? | JD 解析不频繁,1-2 个足够 |
| Resume Worker 太少? | 简历量大,需要 5-10 个并行 |
| Match Worker 挂了? | Orchestrator 检测超时,重新分发 |
| JD 还没解析完就匹配? | Orchestrator 等待 t1 完成后再分发 t3 |
| 两个 Worker 抢资源? | Redis Queue 内部实现,不暴露给 Agent |
7. 与传统 Orchestrator-Workers 的区别
| 传统模式 | 本设计 |
|---|---|
| Orchestrator 决定「谁来执行」 | Orchestrator 只决定「做什么」,Queue 决定「谁来做」 |
| Worker 是通用执行者 | Worker 是专业化 agent(JD专用、Match专用) |
| 结果直接返回 | 统一经过 Orchestrator 聚合 |
这样设计的好处是:专业化比通用化更精准,JD Worker 专注理解招聘需求,Match Worker 专注匹配逻辑。
简历知识库存储什么?
1. 数据分层
graph TD
subgraph KB["简历知识库四层架构"]
L1["L1: 原始文件层 (Raw Files)<br/>- PDF/DOCX/HTML/IMG 原文件<br/>- 用途: 存档、复核、OCR 重新处理"]
L2["L2: 结构化数据层 (Structured Schema)<br/>- ResumeSchema (JSON)<br/>- 用途: 业务查询、筛选、排序"]
L3["L3: 语义索引层 (Semantic Index)<br/>- 向量 embedding (技能、工作描述)<br/>- 用途: 语义相似召回"]
L4["L4: 知识图谱层 (Knowledge Graph)<br/>- 技能实体关系<br/>- 候选人-公司-技能-项目的图关系<br/>- 用途: 跨维度推理、同义词扩展"]
end
L1 --> L2 --> L3 --> L4
2. 存储选型与数据内容
| 存储层 | 技术选型 | 存储内容 |
|---|---|---|
| L1 原始文件 | S3 / Blob Storage | PDF/DOCX/IMG 原始文件 |
| L2 结构化数据 | PostgreSQL | ResumeSchema JSON + 业务字段 |
| L3 向量索引 | Weaviate / Pinecone | skill_embedding, description_embedding |
| L4 知识图谱 | Neo4j / Amazon Neptune | 技能图谱、公司关系、项目关系 |
3. 读写分离
graph TD
W["写操作 (简历上传、JD创建)"]
P["Primary Write DB (PostgreSQL Primary)"]
R1["Replica 1 (读)"]
R2["Replica 2 (读)"]
RD["读操作 (筛选、搜索)"]
PB["PgBouncer (连接池, 读写分离路由)"]
W --> P
P -- "Streaming Replication" --> R1
P -- "Streaming Replication" --> R2
RD --> PB
PB -- "Read 1" --> R1
PB -- "Read 2" --> R2
高可用
1. 基建
| 组件 | 高可用策略 | 故障切换时间 |
|---|---|---|
| 多区域部署 | 主从 + 异步复制 | 分钟级 |
| Kubernetes Cluster | 多 Master 节点 | 秒级 |
| 负载均衡器 | 健康检查 + 自动摘除 | 秒级 |
| DNS | TTL 多值 + 智能解析 | 分钟级 |
2. 数据库层高可用
graph TD
C["Client"]
PB["PgBouncer (连接池 + 读写分离路由)"]
P["Primary (写)"]
S1["Standby 1 (热备)"]
S2["Standby 2 (热备)"]
RR["Read Replica 3 (读副本,用于报表/离线分析)"]
C --> PB
PB --> P
S1 -- "同步" --> P
S2 -- "同步" --> P
P -- "Streaming Replication" --> RR
note["自动故障切换: PgPool-II 或 Patroni + etcd"]
3. MQ高可用
redis cluster 哨兵
4. 知识库高可用
读写分离
高并发
graph TD
U["简历上传"]
GW["API Gateway (限流 + Auth + 路由)"]
S3["Upload S3 (Pre-signed URL, 避开 API 服务器)"]
RS["Redis Stream (消息队列)<br/>resume:uploaded 消息"]
subgraph Workers["Worker Groups (按分区并行消费)"]
GA["Worker Group A (处理分区 0-99)"]
GB["Worker Group B (处理分区 100-199)"]
GN["Worker Group N (处理分区 200-299)"]
end
subgraph CG["Consumer Group (消费者组)<br/>同一组内竞争消费同一分区,不同组独立处理"]
CA["Group A: 解析 → 写入 PG → 触发 Vector Agent"]
CB["Group B: 解析 → 触发 Graph Agent → 更新质量分"]
end
U --> GW --> S3 -- "Async Event" --> RS
RS --> GA
RS --> GB
RS --> GN
GA --> CA
GB --> CB
GN --> CA
性能指标
| 指标 | 正常范围 | 告警阈值 | 处理策略 |
|---|---|---|---|
| API P99 Latency | < 500ms | > 1s | 扩容 + 降级 |
| API Error Rate | < 0.1% | > 1% | 熔断 + 告警 |
| Queue Depth | < 1000 | > 5000 | 扩容 Worker |
| CPU Utilization | 40-70% | > 80% | 扩容 |
| Memory Usage | 50-70% | > 85% | 扩容 |
| DB Connections | < 80% | > 90% | 连接池调优 |
| Vector DB QPS | < 80% capacity | > 90% | 扩容副本 |
| 整体服务架构 |
graph TD
subgraph Regions["全球多区域部署架构"]
RA["Region A (主: 北京)"]
RB["Region B (备: 上海)"]
RC["Region C (备: 广州)"]
end
RA <-. "同步" .-> RB
RB <-. "异步" .-> RC
GSLB["Global Load Balancer (GSLB)<br/>DNS 智能解析 + 健康检查 + 流量调度"]
subgraph GW["API Gateway (Kong/AWS API GW)"]
G1["Rate Limiter / Auth+JWT / Router / 熔断器"]
G2["Rate Limiter / Auth+JWT / Router / 熔断器"]
end
K8S["Kubernetes Cluster (K8s)<br/>Ingress Controller + API Pod (3+ replicas)<br/>HPA 自动扩缩容 (CPU/Memory/RPS)"]
subgraph Workers["Worker Pools"]
RW["Resume Worker Pool (5-20) HPA"]
JW["JD Worker Pool (2-8) HPA"]
MW["Match Worker Pool (5-20) HPA"]
end
REDIS["Redis Cluster (Queue + Cache)<br/>Master + Slave1 + Slave2<br/>Sentinel / Cluster Mode"]
subgraph Storage["存储层"]
PG["PostgreSQL (主从+读 replica)"]
WV["Weaviate (多副本)"]
N4J["Neo4j (多副本)"]
end
RA --> GSLB
RB --> GSLB
RC --> GSLB
GSLB --> G1
GSLB --> G2
G1 --> K8S
G2 --> K8S
K8S --> RW
K8S --> JW
K8S --> MW
RW --> REDIS
JW --> REDIS
MW --> REDIS
REDIS --> PG
REDIS --> WV
REDIS --> N4J
生产环境架构选型与规划
1.1 Redis 作为 MQ 的核心问题
┌─────────────────────────────────────────────────────────────────┐
│ Redis 不适合做主力 MQ 的原因 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 消息持久化问题 │─┐
│ ├── Redis 持久化是"建议性"的,断电可能丢消息 │
│ ├── AOF + fsync every sec 模式下,最坏丢 1 秒数据 │┤
│ └── 生产环境:简历数据丢了 = 灾难 ││
│ │──┤
│ 2. 消息堆积能力 │ │
│ ├── Redis 所有数据在内存,堆积消息 = OOM │┤
│ ├── 简历处理峰值 10万/小时,峰谷比 10:1 │ │
│ └── 专业 MQ 磁盘持久化,堆积百万级消息 │┤
│ │ │
│ 3. 消息顺序保证 │─┘
│ ├── Redis Stream 虽然有 consumer group,但无法保证全局顺序 │
│ ├── 批量筛选需要:JD 解析先于匹配,简历按序处理 │
│ └── Kafka/RocketMQ 靠分区保证顺序 │
│ │
│ 4. 事务与exactly-once │
│ ├── 简历解析完成 → 写入 PG → 写入向量库 → 发送通知 │
│ ├── Redis 无法保证跨系统的分布式事务 │
│ └── RocketMQ 事务消息 = 本地事务 + 消息投递原子性 │
│ │
│ 5. 延迟/定时消息 │
│ ├── 简历重试、HR 通知提醒、定时任务 │
│ ├── Redis 用 Sorted Set 模拟,复杂且不可靠 │
│ └── RocketMQ 延迟消息是原生支持 │
│ │
│ 6. 死信队列 / 消息回溯 │
│ ├── 消费失败 → 死信队列 → 人工处理 │
│ ├── Redis 需要自己实现 │
│ └── Kafka/RocketMQ 原生支持 │
│ │
└─────────────────────────────────────────────────────────────────┘
1.2 消息队列选型对比
| 特性 | RocketMQ | Kafka | Redis Stream |
|---|---|---|---|
| 消息持久化 | 磁盘,TB 级 | 磁盘,PB 级 | 内存优先 |
| 吞吐量 | 10万/秒 | 100万/秒 | 20万/秒 |
| 消息堆积 | 强,磁盘无限 | 极强,保留 7-30 天 | 弱,受内存限制 |
| 顺序消息 | 支持全局顺序 | 分区内有序 | 不保证 |
| 事务消息 | 原生支持 | 需自研 | 不支持 |
| 延迟消息 | 原生支持 | 需插件 | 模拟实现,可用 |
| 死信队列 | 原生 | 原生 | 需自研 |
| 消息回溯 | 支持 | 支持 | 不支持 |
| 运维难度 | 中 | 高 | 低 |
| 单条消息成本 | 中 | 低 | 低 |
| 1.3 推荐方案 |
┌─────────────────────────────────────────────────────────────────┐
│ 生产环境 MQ 选型建议 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ RocketMQ (推荐) ││
│ │ ││
│ │ ✅ 事务消息(简历处理链路的原子性) ││
│ │ ✅ 延迟消息(重试、通知调度) ││
│ │ ✅ 顺序消息(简历按序处理) ││
│ │ ✅ 消息堆积(峰值削峰) ││
│ │ ✅ 死信队列(失败处理) ││
│ │ ✅ Java 生态(团队技能匹配) ││
│ │ ││
│ │ 适用场景: ││
│ │ - 简历处理 Pipeline (解析→标准化→向量化→存储) ││
│ │ - 筛选任务分发 ││
│ │ - HR 通知消息 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Kafka (备选/大数据场景) ││
│ │ ││
│ │ ✅ 极高吞吐量(HR SaaS 多租户日志、审计日志) ││
│ │ ✅ 消息回溯(重新处理历史简历) ││
│ │ ✅ 生态丰富(与 Flink/Spark 集成) ││
│ │ ││
│ │ 适用场景: ││
│ │ - 简历原始文件上传事件 ││
│ │ - 审计日志/操作日志 ││
│ │ - 实时数据分析 (筛选漏斗、HR 行为分析) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Redis (仅限缓存,不做 MQ) ││
│ │ ││
│ │ ✅ Session 缓存 ││
│ │ ✅ 计算结果缓存 (JD Schema, Match Results) ││
│ │ ✅ 分布式锁 ││
│ │ ✅ HPA 指标存储 (队列深度采集) ││
│ │ ❌ 不做消息队列 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
二、其他架构改进点
2.1 向量数据库选型问题
┌─────────────────────────────────────────────────────────────────┐
│ Weaviate/Pinecone 生产问题 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 问题 1: Weaviate 是 Java 服务,内存消耗大 │
│ ├── 官方建议 16GB+ 内存 │
│ └── 多副本 = 内存成本乘以副本数 │
│ │
│ 问题 2: 混合搜索的局限 │
│ ├── BM25 + Vector 融合不如专业方案 │
│ └── RRF 融合是自己实现,后期维护成本高 │
│ │
│ 推荐方案: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Milvus (推荐) │ │
│ │ ├── 专为向量设计,性能优 │ │
│ │ ├── 混合搜索插件丰富 │ │
│ │ ├── GPU 加速支持 │ │
│ │ └── 国产开源,社区活跃 │ │
│ │ │ │
│ │ 或: │ │
│ │ │ │
│ │ PgVector (简单场景) │ │
│ │ ├── PostgreSQL 生态,运维简单 │ │
│ │ ├── <10万向量足够 │ │
│ │ └── 不足: 扩展性一般 │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 知识图谱必要性评估
┌─────────────────────────────────────────────────────────────────┐
│ 知识图谱真的是必须的吗? │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 当前设计: Neo4j 存储技能图谱 │
│ │
│ 生产问题: │
│ ├── 技能图谱数据从哪来?维护成本极高 │
│ ├── "微服务" 的同义词: [分布式系统, SOA, MSA] 谁来维护? │
│ ├── JD 和简历的技能匹配,用 embedding 语义相似度已经能解决 80% │
│ └── 引入 Neo4j = 多一套存储 + 多一套运维 │
│ │
│ 简化方案: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 用 Embedding + 同义词表替代知识图谱 │ │
│ │ │ │
│ │ skill_synonyms = { │ │
│ │ "微服务架构": ["分布式系统", "SOA", "MSA", "服务化"], │ │
│ │ "Python": ["Python3"], │ │
│ │ "机器学习": ["ML", "Machine Learning"] │ │
│ │ } │ │
│ │ │ │
│ │ 匹配时: │ │
│ │ 1. 直接匹配 │ │
│ │ 2. 同义词扩展匹配 │ │
│ │ 3. 向量语义相似度兜底 │ │
│ │ │ │
│ │ 收益: -Neo4j 依赖,-运维成本,+系统简单性 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 结论: 知识图谱适合前期 POC 验证,业务验证跑通后再考虑引入 │
│ │
└─────────────────────────────────────────────────────────────────┘
2.3 多租户隔离问题
┌─────────────────────────────────────────────────────────────────┐
│ 当前架构多租户隔离不足 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 问题: │
│ ├── 所有租户共用一套 PostgreSQL/Weaviate/Redis │
│ ├── 租户 A 的简历可能通过向量相似度 召回 租户 B 的简历 │
│ ├── 数据泄露风险 │
│ └── 资源竞争: 租户 A 批量导入影响 租户 B 的筛选延迟 │
│ │
│ 改进方案: │
│ │
│ 方案 A: Schema 隔离 (简单场景) │
│ ├── PostgreSQL: schema per tenant │
│ ├── Weaviate: tenant_id 字段隔离 │
│ └── 优点: 简单,缺点: 跨租户查询困难 │
│ │
│ 方案 B: Namespace 隔离 (推荐) │
│ ├── 每个租户独立的 namespace │
│ ├── 消息队列: tenant_id 分区 │
│ ├── 向量库: namespace 隔离 │
│ └── 优点: 隔离性好,缺点: 资源成本 x N │
│ │
│ 方案 C: 资源配额 + 逻辑隔离 (成本敏感场景) │
│ ├── 租户级别资源配额 (QPS limit, storage quota) │
│ ├── 租户 ID 标记所有数据 │
│ ├── 查询时强制加 tenant_id 过滤 │
│ └── 优点: 成本低,缺点: 隔离性弱 │
│ │
│ 推荐: 初期用方案 C,验证业务后升级到方案 B │
│ │
└─────────────────────────────────────────────────────────────────┘
三、生产环境关键改进
3.1 LLM 调用的高可用问题
当前设计: 直接调用 OpenAI/Anthropic API
生产问题:
- OpenAI API 故障 = 系统不可用
- API 限流 = 批量处理中断
- Token 成本不可预测 = 预算失控
- API 响应时间波动大 = SLA 无法保证
改进方案 (LLM Gateway 架构):
graph LR
subgraph Providers["模型提供方"]
O["OpenAI GPT-4"]
A["Anthropic Claude"]
AZ["Azure OpenAI"]
L["本地模型"]
end
subgraph GW["LLM Gateway"]
R["多模型路由 (策略模式)<br/>Primary/Secondary / 成本优先 / 延迟优先"]
CB["熔断器 (每模型独立)<br/>失败率 > 50% → 切换"]
RL["限流 + 配额管理<br/>per tenant QPS / per model limit"]
CACHE["响应缓存 (semantic cache)<br/>相同 JD 解析结果缓存, LLM 调用 -50%"]
end
O --> R
A --> R
AZ --> R
L --> R
开源方案:
- LiteLLM (Python) - 支持 50+ LLM API
- PortKey (SaaS) - LLM 网关 + 分析
- GPTCache - 本地语义缓存 3.2 简历处理链路的 Exactly-Once 问题
当前设计的问题:
简历上传 → Resume Worker → Ingestion → Vector → Graph → 通知
│ │ │ │ │
│ 写入 PG 写入向量 写入图 发邮件
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
可能失败点: 任意一步失败,简历状态不一致
问题场景:
- Ingestion 成功,但 Vector Agent 失败 → PG 有数据,向量库没有
- 重试后,向量库重复写入 → 数据重复
- Graph Agent 失败 → 技能图谱不完整
改进方案: RocketMQ 事务消息
sequenceDiagram
participant P as Producer
participant B as Broker
participant C as Consumer
P->>B: 发送 half 消息
P->>P: 执行本地事务 (写入 PG)
P->>B: commit/rollback
B->>C: 投递消息
C->>C: 处理向量/图/通知
C-->>B: commit
事务状态表设计 (resume_transaction):
- transaction_id: UUID
- candidate_id: UUID
- status: PENDING|COMMITTED|ROLLBACK
- steps:
- ingestion: DONE
- vector: PENDING
- graph: PENDING
- notification: PENDING
-
created_at, updated_at
四、完整改进后的架构图
graph TD
subgraph Access["接入层"]
K1["Kong API GW (区域 A)<br/>Rate Limit / Auth / WAF"]
K2["Kong API GW (区域 B)<br/>Rate Limit / Auth / WAF"]
K3["Kong API GW (区域 C)<br/>Rate Limit / Auth / WAF"]
end
subgraph LLMGW["LLM Gateway (LiteLLM)"]
O["OpenAI"]
C["Claude"]
AZ["Azure"]
CACHE["语义缓存 (GPTCache)"]
end
subgraph MQ["RocketMQ (事务消息 + 延迟消息)"]
P["resume:parse<br/>延迟重试 / 死信队列"]
S["resume:sync<br/>跨系统同步 / 顺序保证"]
M["match:batch<br/>批量筛选 / 结果聚合"]
end
subgraph Workers["处理层"]
RW["Resume Worker Pool (HPA)<br/>格式解析 / LLM 提取"]
IA["Ingestion Agent<br/>事务写入 / 去重检测 / 质量评分"]
MW["Match Worker Pool (HPA)<br/>向量召回 / 深度匹配 / 排序"]
end
subgraph Storage["存储层 (多租户隔离)"]
PG["PostgreSQL (读写分离, schema per tenant)"]
M["Milvus (向量存储, namespace per tenant)"]
RD["Redis (缓存: Session/Results/Metrics)"]
end
subgraph Obs["可观测性层"]
J["Jaeger (Trace)"]
PR["Prometheus (Metrics)"]
G["Grafana (Dashboard)"]
end
K1 --> LLMGW
K2 --> LLMGW
K3 --> LLMGW
LLMGW --> MQ
MQ --> RW
MQ --> IA
MQ --> MW
RW --> IA
IA --> MW
RW --> Storage
IA --> Storage
MW --> Storage
Storage --> Obs
五、改进清单汇总
| 问题 | 当前方案 | 推荐改进 | 优先级 |
|---|---|---|---|
| MQ 选型 | Redis Stream | RocketMQ (事务消息) | P0 |
| LLM 可用性 | 直接调用 API | LLM Gateway (多模型+熔断+缓存) | P0 |
| 向量库选型 | Weaviate | Milvus (性能+GPU支持) | P1 |
| Exactly-Once | 无 | RocketMQ 事务消息 | P0 |
| 多租户隔离 | 逻辑隔离 | Namespace 隔离 + 资源配额 | P1 |
| 知识图谱 | Neo4j | 简化为同义词表 + Embedding | P2 |
| 监控告警 | 简单指标 | 全链路追踪 + 业务指标 | P1 |
| 成本控制 | 无 | LLM Gateway 配额 + 缓存 | P1 |
六、总结
| 维度 | 原设计 | 改进后 |
|---|---|---|
| MQ | Redis Stream | RocketMQ |
| 向量库 | Weaviate | Milvus |
| LLM | 直连 API | LLM Gateway |
| 多租户 | 逻辑隔离 | Namespace + 配额 |
| 事务 | 无 | RocketMQ 事务消息 |
| 图谱 | Neo4j | 同义词表 |
| 缓存 | 无 | GPTCache |
| 核心原则: |
- 生产环境 Redis 不做 MQ - 用 RocketMQ
- LLM 调用必须加网关 - 可用性 > 成本
- 先简化再迭代 - 知识图谱不是必须项
- 多租户隔离要提前考虑 - 后期改造成本高