Appearance
服务拆分与租户隔离:边界怎么划才不会返工
1. 本节产出
一份服务拆分方案(含边界判定依据、服务契约、ADR),以及租户隔离的三个层次实现(数据/配额/模型)。
2. 前置依赖
3. 为什么 AI 时代的服务边界和传统微服务不同
传统微服务按「业务领域」拆(订单、库存、用户)。AI 服务不同,因为:
| 差异 | 影响 |
|---|---|
| 共享昂贵的外部依赖(模型、向量库) | 拆分后重复调用 = 重复花钱 |
| 计算密集但无状态(生成)vs 数据密集(检索) | 两者的伸缩模式完全不同 |
| 任务长耗时(Agent 分钟级) | 与 HTTP 请求超时模型不匹配 |
| 效果依赖共享配置(prompt、模型版本) | 配置分散会导致效果不一致 |
所以不能简单按业务领域拆。
4. 核心原理
4.1 推荐的拆分维度
按「技术特性 + 变更频率」拆,而不是按业务领域:
1. model-gateway 模型网关(横切,变更极少)
2. rag-service 检索服务(数据密集,随知识库变更)
3. agent-service 编排服务(计算密集,随业务流程变更)
4. ingest-service 灌库服务(批处理,独立伸缩)
5. admin-service 租户/配额/计费(业务配置)
6. eval-service 评测服务(离线,独立部署)判定依据:
| 服务 | 为什么独立 |
|---|---|
| model-gateway | 横切关注点,所有服务共用,变更要极其谨慎 |
| rag-service | 数据密集,需要靠近向量库,伸缩模式不同 |
| agent-service | 长耗时任务,需要独立的线程池与超时策略 |
| ingest-service | 批处理,资源消耗大,不能影响在线 |
| eval-service | 离线任务,可以停机维护 |
4.2 一个反例:按业务领域拆的后果
错误拆法:
order-ai-service (订单相关的 AI 能力)
marketing-ai-service(营销相关的 AI 能力)
后果:
├─ 两个服务各自接模型 → Key 分散、无法统一限流
├─ 两个服务各自建向量库 → 同样的文档灌两遍、Embedding 花两次钱
├─ prompt 版本各管各的 → 效果不一致,无法统一优化
└─ 新增一个业务领域 → 又复制一套结论:按业务拆会导致 AI 基础设施被复制 N 份。 正确做法是按技术特性拆,业务差异通过配置和 prompt 表达。
4.3 服务契约
yaml
# rag-service 的契约(OpenAPI 片段)
POST /rag/query
request:
tenantId: string (required, from JWT)
query: string
topK: int (default 5)
filters: object (optional)
response:
chunks: [{ text, score, sourceId, titlePath }]
rejected: boolean
tokensUsed: int
# agent-service 的契约
POST /agent/run
request:
tenantId, userId, goal, caseType, limits
response:
runId, status, answer, trace, tokensUsed
POST /agent/{runId}/approve契约里必须带 tenantId 和 tokensUsed:前者保证隔离,后者保证成本可归因。
4.4 租户隔离的三个层次
| 层次 | 内容 | 实现 |
|---|---|---|
| 数据隔离 | 向量库、文档的隔离 | 元数据过滤(02-12)+ 存储前缀 |
| 配额隔离 | Token/金额/速率 | 网关配额(04-03) |
| 模型隔离 | 不同租户可用不同模型 | 路由策略按租户(04-02) |
数据隔离是硬要求,另外两个是商业策略。
4.5 数据隔离的三种部署模式
| 模式 | 做法 | 成本 | 隔离强度 |
|---|---|---|---|
| 共享库 + 元数据过滤 | 一张表,tenantId 过滤 | 最低 | 中(依赖代码正确性) |
| 共享实例 + 分表/分 schema | 每租户一个 schema | 中 | 高 |
| 独立实例 | 每租户一套 | 最高 | 最高 |
推荐默认「共享库 + 元数据过滤」,对有合规要求的租户提供独立 schema 或独立实例(作为付费选项)。
共享模式的前提:必须有自动化断言测试证明隔离生效(02-12 的 TenantIsolationIT)。这是选型能否用共享模式的唯一依据。
5. 代码走查
5.1 服务间调用(带租户透传)
java
// agent-service 调用 rag-service
@Service
public class RagClient {
private final WebClient http;
public List<Chunk> query(String query, int topK) {
return http.post()
.uri(ragServiceUrl + "/rag/query")
// 关键:租户上下文必须透传
.header("X-Tenant-Id", TenantContext.require())
.header("X-Trace-Id", TraceContext.currentId())
.bodyValue(new RagQuery(query, topK))
.retrieve()
.body(RagResponse.class)
.chunks();
}
}租户透传靠 HTTP Header + 服务端 Filter 重建上下文,不能靠参数传递(容易漏)。
5.2 契约测试(防止服务间悄悄改坏)
java
// ch04-platform/src/test/java/com/aitech/platform/RagServiceContractTest.java
@Test
void rag服务契约() {
// 用 Pact 或简单的集成测试固定契约
var resp = ragClient.query("年假怎么算", 5);
assertThat(resp.chunks()).isNotNull();
assertThat(resp.tokensUsed()).isGreaterThan(0);
// 契约里承诺的字段必须存在,否则 CI 失败
}5.3 隔离断言(跨服务)
java
@Test
void 跨服务的租户隔离() {
// agent-service 调用 rag-service 时,租户必须被正确传递
TenantContext.set("acme");
var r = agentService.run(goal);
assertThat(r.trace().allChunks())
.allSatisfy(c -> assertThat(c.tenantId()).isEqualTo("acme"));
}这个测试很关键:它验证租户上下文在跨服务调用中没有丢失——这是分布式系统里最容易出错的地方。
5.4 ADR 示例
markdown
# ADR-003:服务拆分方式
## 状态:已接受
## 背景
需要支持多个业务团队接入 AI 能力。
## 决策
按技术特性拆分(gateway / rag / agent / ingest / admin / eval),
不按业务领域拆分。业务差异通过 prompt 配置与工具集表达。
## 备选
按业务领域拆分(order-ai / marketing-ai):会导致 AI 基础设施
被复制 N 份,成本与维护成本均不可接受。
## 后果
- 正:基础设施复用、成本可归因、升级一次全生效
- 负:单个服务会成为多个业务的共享依赖,需要更强的可用性保障6. 跑起来
bash
git checkout ch04-04-service-split
docker compose up -d # 多服务 + PG + Redis
mvn -q test -Dtest=ServiceSplitIntegrationTest| 检查项 | 通过标准 |
|---|---|
| 服务可独立部署 | 每个服务能单独重启 |
| 租户透传 | 跨服务调用后租户上下文正确 |
| 隔离断言 | 跨服务隔离测试通过 |
| 契约测试 | 契约变更时 CI 失败 |
| ADR 齐全 | 拆分决策有记录 |
7. 生产避坑
- 不要按业务领域拆 AI 服务。这会导致模型接入、向量库、prompt 版本被复制 N 份,成本和一致性都失控。按技术特性拆,业务差异用配置表达。
- 租户上下文跨服务必须靠 Header 透传 + Filter 重建,不能靠方法参数。靠参数传递时,任何一处忘记传就是越权漏洞,而这种漏洞在单服务测试中完全测不出来。
- 共享库模式的隔离必须有自动化断言。共享存储 + 元数据过滤的隔离强度依赖代码正确性,任何一处漏过滤就是数据泄露。没有断言测试就不要用共享模式——这是选型的前提条件,不是可选项。
8. 延伸与锚点
- 思考题:有客户要求「数据绝对不能出内网」,怎么部署?(答案在下一课时:私有化部署)
- 代码锚点:
git checkout ch04-04-service-split - 下一课时:04-05 私有化部署与推理选型
- 对应课件:L04-04 服务拆分