☰
多租户RAG架构设计:隔离、共享与知识形态的平衡
2026/10/8 4:40:25 网站建设 项目流程

1. 为什么我放弃了现成方案,决定自研一个多租户 RAG

如果你正在做企业内部知识助手,或者准备给不同客户交付一套带知识库问答的 SaaS 产品,大概率会撞上同一个问题:多个租户的知识要隔离,但 GPU 和向量库资源不可能每个租户都复制一套。这个项目做完之后我最大的感慨是——多租户 RAG 不是给 RAG 加一个 tenant_id 字段就完事,它牵动存储、索引、检索链路、权限模型乃至部署形态的整套设计。UniRAG 是我在团队内部从零搭起来的一套多租户 RAG 框架,这篇文章把整个设计过程、取舍逻辑和踩坑细节一次讲透。

先交代一下背景。我们团队做的事是给不同客户提供知识库问答能力,客户之间数据完全隔离,每个租户可能有自己的文档、表格、甚至知识图谱。早期我们直接在一套开源 RAG 框架上改,往向量表里塞租户字段,看起来能用,但等到租户数量上来、知识量上来,各种问题就爆发了:向量检索召回串数据、索引更新互相拖累、某个大租户跑批把全站请求拖到超时、不同租户对"知识"的形态需求根本不一样——有的租户就是 PDF 文档,有的租户想直接查结构化数据库,还有的租户手里是一堆带关系的业务实体。这时候你才意识到,所谓"多租户 RAG"真正要解决的是三个层面的事情:数据隔离、算力共享、知识形态的统一。

1.1 多租户到底卡在哪:RAG 的三大瓶颈

先说清楚 RAG 本身当前的瓶颈,否则后面对设计的讨论没有依据。第一个瓶颈是知识库的碎片化。传统 RAG 把文档切块塞进向量库,切完就完了,块与块之间没有关系,碰到需要跨文档推理的问题就露馅。这也是为什么圈子里现在都在聊构建类 KG 的知识库、为什么有人非要用图谱补一层。我在调研阶段看了不少 rag 瓶颈相关讨论,大家的共识基本一致:单靠向量相似度支撑不起复杂问答。

第二个瓶颈是检索质量的不可控。召回的 TopK 里经常混进不相关片段,重排序模型能缓解但没法根治,而且重排序本身也是资源消耗大户。更麻烦的是,检索质量很大程度上取决于 chunk 策略和 embedding 模型,这两样东西恰恰是多租户场景下最难统一的——不同租户的知识领域差异很大,一个法律知识库和一个电商客服知识库,最优的 chunk 粒度完全不一样。

第三个瓶颈才是多租户特有的:隔离与共享的矛盾。隔离指的是租户数据不能互相看到,共享指的是 embedding 模型、向量库、GPU 推理这些资源最好大家共用。租户少的时侯直接给每个租户开一套独立 RAG 实例就行,但租户一多,资源开销、运维复杂度、索引更新成本都会线性膨胀。UniRAG 的设计初衷就是想找到一个"一套底座、多租户共用、但逻辑上完全隔离"的平衡点。

1.2 团队内部的方案对比:dify 社区版、开源框架与自研

既然要做选型,我把市面上能走的路都趟了一遍。第一类是 dify 社区版这类平台型工具。dify 社区版 1.10 确实开始支持多租户能力,但它的多租户更多是"工作空间"级别的隔离,每个空间有自己的知识库、应用和 API Key。好处是上手快,坏处是对于我们要交付给客户做私有化定制的场景,dify 的抽象层级太固定,底层的检索链路、知识库类型、权限模型很难按客户需求调整。如果你只需要快速搭一套内部工具,dify 足够;如果要做产品化,就会撞上定制天花板。

第二类是各种开源 RAG 框架,包括一些所谓的"企业级 RAG"。它们的问题出在两个地方:一是多租户支持基本靠"全网搜一下 rag 知识库怎么搭"那种单租户教程,框架层面很少内置租户路由和资源配额;二是知识库形态单一,要么纯文档向量检索,要么接一个外部图数据库做 GraphRAG,像我们需要的"文档 + 结构化数据 + 图谱"三种形态统一在一个问答入口里,几乎没有现成方案。

第三类才是自研。这也是 UniRAG 项目的真正起点。我们定的调子是:不重复造 RAG 轮子,检索、重排、生成还是用成熟组件,但存储隔离、租户路由、知识库模型、权限注入这几层必须自己设计。

1.3 UniRAG 的目标边界:哪些功能做,哪些坚决不做

任何自研项目最怕的就是范围失控。UniRAG 立项时我就定了三做三不做。

做:租户级的数据隔离与权限过滤、文档/结构化/图谱三类知识库的统一建模、共享资源池下的性能隔离。 不做:不重新训练 embedding 模型,不做分布式向量库(初期直接用成熟的向量数据库,把分布式交给云厂商),不做完整的 RAG 工作流编排(那又变成 dify 了)。

这个边界很重要。很多人自研 RAG 项目最容易犯的错就是想从头到尾全自研,结果半年过去连检索都还不稳定。我的经验是,非核心链路尽量用成熟组件,把精力集中在"多租户"这个真正的差异化点上。

2. 租户隔离的第一道防线:向量库与元数据策略

多租户 RAG 的第一道设计决策发生在存储层。你要回答一个核心问题:租户数据在向量库里到底怎么放?放的方式直接决定检索时能不能高效隔离、索引更新时会不会互相影响。

2.1 向量库选型:collection 隔离、分区隔离还是 metadata 过滤

市面上的向量数据库大致给出三种隔离粒度。第一种是 collection 级隔离,一个租户一个 collection,隔离最彻底,但 collection 数量一多,后端分片管理和内存开销就上来了。第二种是分区隔离,同一张表按租户分区,比 collection 轻一些,但能不能做到依赖具体产品的实现。第三种是 metadata 过滤,所有租户的数据在同一张表里,靠向量记录上的租户标签做检索时过滤。

我在项目里做了个简单测试对比:当租户数量在 50 个以下时,三种方案性能差距很小;到了 200 个租户、每个租户平均 10 万份文档块时,metadata 过滤方案的召回精度问题开始暴露——过滤条件写得太粗会把别的租户的数据扫进来,写得太细则过滤本身变成一次全表扫描。

最终 UniRAG 用的是"collection 为主、metadata 为辅"的组合:每个租户默认一个独立的向量 collection,确保数据物理隔离;但在需要跨租户检索的场景(比如运营后台做全局问答分析),才用 metadata 过滤走一个只读的聚合索引。这个取舍放弃了"一套索引服务所有租户"的极致节省,换来了最不容易出事的隔离边界。

2.2 租户元数据的数据模型设计

存储结构定了,接下来是租户元数据模型。很多项目在这里只保存一个 tenant_id,后面想做高级功能全部抓瞎。UniRAG 的租户元数据分成三层:

基础层:租户 ID、租户名称、状态(启用/停用/欠费冻结)、创建时间。 资源配置层:分配的向量 collection 名称、允许使用的知识库类型列表、模型路由组 ID(后面检索生成会用不同的模型)、配额上限(文档总数、索引大小、每分钟请求数)。 策略层:检索 TopK 上限、是否允许跨知识库检索、重排序模型的开关、以及权限过滤规则。

这里我特别想强调资源配置层的重要性。多租户系统最怕的就是某个租户把共享资源池打爆。我在设计时给每个租户定了三档配额——基础档、标准档、高并发档,配额信息缓存在 Redis 里,请求进来先查配额再查数据,两个逻辑分开。这套设计在压力测试阶段帮了大忙,后面压测复盘部分会详细说。

2.3 共享索引与租户路由:如何做到全租户召回但零串租

存储层还有一个容易忽略的点:向量索引的更新策略。在多租户场景下,你不能让某个租户的大批量文档导入任务把整个搜索服务拖垮。UniRAG 的做法是异步索引队列——当某个租户上传新文档,先把文档写入对象存储,返回"处理中"状态,后台任务负责解析、切片、embedding、写入该租户的 collection。每个租户的索引任务占一个独立队列,配合信号量控制并发,这样大租户跑批不会影响小租户的实时检索。

租户路由则放在接入层。每一次问答请求都会携带租户身份,经过网关层解析后,请求被路由到该租户对应的检索链路口袋。路由信息不是简单查表,而是一段配置化的检索计划,内容包括:从哪个 collection 取向量、要不要走额外关键词检索、要不要查图谱子图、用什么重排序模型。这套路由设计的价值在后期做不同租户的 A/B 实验时体现得淋漓尽致——只要改一段配置,就能让特定租户走新的检索链路,而不用动核心代码。

3. 知识库建模的取舍:文档知识库、结构化知识库与图谱(KG)知识库

RAG 项目做到中期基本都会遇到同一个坎:用户不满足于"文档问答",开始问"为什么这两个数据对不上""这个客户的上一个订单和投诉记录有什么关系"。这些问题的答案不在 PDF 里,而在结构化数据里,在实体关系里。这就是 UniRAG 知识库建模要解决的领域。

3.1 三类知识库的定位差异与典型应用场景

先把概念理清。rag 知识库通常指传统的文档型知识库,把 PDF、Word、Markdown 切片成向量块,适合"根据文档内容回答问题";结构知识库指表格、数据库、API 里的结构化记录,适合精确查询、聚合统计、条件筛选;kg 知识库(知识图谱库)则围绕实体和关系组织知识,适合多跳推理、关联分析、路径查询。

它们的应用场景差异非常明显。比如同样是回答"这个客户的账户状态是否正常",文档知识库大概率会从一份服务协议 PDF 里找出"账户状态"的笼统描述,结构化知识库能直接查出该客户的账户表记录,图谱知识库则能沿着"客户-订单-售后工单-客服"的关系链给出上下文丰富的答案。

所以问题不是"哪种知识库更好",而是如何让一套问答系统按问题类型自动选择正确的知识源。我在 UniRAG 里做了一个"知识路由"模块,请求进来后先做一次轻量级意图判断——是事实型问题、统计型问题、还是关系推理型问题——再决定走哪个知识库通道。这个判断不一定每次都对,但结合下面的混合检索设计,能有效提升整体回答质量。

3.2 如何在 UniRAG 里统一三类知识源的加载与解析

不同知识库的加载链路差异很大。文档知识库需要解析、清洗、切片、embedding;结构化知识库需要建表映射、配置查询模板;图谱知识库需要从文本或结构化数据里抽取实体和关系。

UniRAG 的做法是定义统一的"知识源适配器"接口,每个适配器负责把一种数据源转换成标准的"知识单元"。文档适配器输出向量块;结构化适配器输出"表 + 查询模板 + 自然语言到查询语句的转译规则";图谱适配器输出"实体节点 + 关系边 + 子图查询模板"。统一的接口让上层检索模块不需要关心底层数据源是什么,只需要调用统一的知识查询接口。

这里我给一个实际的适配器配置伪代码,方便理解:

data_source: type: structured_table engine: mysql connection: ${TENANT_DB_CONN} tables: - name: customer_order primary_key: order_id fields: - order_id - customer_name - amount - status query_templates: - intent: query_order_status sql: "SELECT status FROM customer_order WHERE order_id = ?" - intent: sum_order_amount sql: "SELECT SUM(amount) FROM customer_order WHERE customer_name = ?"

类似地,文档适配器和高图适配器各自生成自己的知识单元描述,最终都注册到租户的知识源目录里。知识路由模块在检索时根据目录来决定走哪些通道。

3.3 Ontology RAG 的落地:图谱路由与查询改写

热词里提到的 ontology rag 值得单独说一段。ontology(本体)是知识图谱的骨架,定义了实体类型、属性、关系类型以及约束规则。传统的 RAG 图谱方案往往只做实体抽取和关系存储,检索时直接查子图,但这样有个问题——用户问"哪些客户在最近一个月投诉过两次以上",这个查询涉及"客户、投诉工单、时间"三种实体以及聚合统计逻辑,纯子图遍历做不了,必须先经过本体层做语义映射。

UniRAG 在 Ontology 上的落地分三步。第一步是根据租户的业务领域定义本体模型,比如电商租户定义"客户、订单、退货、售后工单"这几类实体,以及它们之间的"创建、包含、关联"关系。第二步是使用本体模型指导实体抽取——抽取时不仅抽实体名,还记录它属于哪个类型、具备哪些属性。第三步是查询改写:当知识路由判定一个问题需要走图谱通道时,会先生成大致的图查询语言(类 Cypher 语法),再结合本体的约束关系做校验和改写。

比如说用户问"去年退货率最高的商品是什么",系统先改写为"查找商品实体,统计其关联退货单数量,按时间过滤,排序",然后才去执行图谱查询。这套流程的精准度比直接向量检索高很多,代价是每个租户需要花时间构建本体模型。对于业务模式清晰的企业客户,这个投入值得,因为问答质量提升是质变级别的。

4. 混合检索链路:多租户下的权限注入与召回排序

知识源统一了,真正的硬骨头在检索链路。多租户 RAG 的检索链路不是"向量召回 + 重排"这么简单,你要同时处理多知识源召回、权限过滤、租户级策略注入,还要保证延迟可控。

4.1 检索链路里的四路召回设计

UniRAG 的检索链路做了四路召回:向量召回、关键词召回、结构化查询召回、图谱子图召回。这四路并行执行,各自返回候选结果,最后合并统一送给重排序模块。

为什么需要四路而不是一路向量召回?因为不同问题适合不同的召回方式。事实型问题向量召回效果好;含精确名称、编号、ID 的问题关键词召回更可靠;统计型问题必须走结构化查询;关系推理型问题则依赖图谱。四路召回的设计参考了经典混合检索思路,但实现里有不少多租户特有的细节,比如每路召回都要带上租户权限上下文,向量召回只能从该租户的 collection 里取数,关键词召回只能搜索该租户的倒排索引,图谱子图查询只能访问该租户在本体实例数据里的子图。

这里我给大家一个检索链路的示意图(用文字描述,不画图):网关层拿到请求 → 路由模块确定租户与检索计划 → 四路并发召回 → 各召回结果统一格式化为标准候选列表 → 进入重排序器 → 生成模块拼接上下文 → 输出答案。每一步之间都有超时控制和日志埋点。

4.2 权限过滤应该放在召回前还是召回后

这是我在设计过程中纠结最久的一个问题。权限过滤放召回前(即查询时就限定数据范围)的好处是效率高、结果干净,坏处是检索逻辑里到处要带上权限条件,复杂度直线上升。权限过滤放召回后(召回完再统一过滤)的好处是检索逻辑简单,坏处是可能召回大量无权访问的数据,既浪费计算又存在数据泄露的窗口期。

我的最终决定是:强隔离权限前置,弱隔离权限后置。所谓强隔离,就是租户级别的数据隔离,这个必须前置,从源头保证不同租户的数据不会进入候选集;所谓弱隔离,是租户内部的文档级权限,比如某个部门的文档只对特定角色可见,这个放在召回后过滤。原因是弱隔离条件往往动态变化,如果全部前置,索引条件会极不稳定,反而影响检索质量。这个分层设计在实际项目里运行得很稳,既保证了安全的底线,又没让检索链路被权限判断拖垮。

4.3 重排序模型与租户级个性化:一个被低估的优化点

很多 RAG 项目把重排序当成一个可选优化,但在多租户场景下,重排序是租户级个性化最重要的落点。不同租户的知识领域、用户群体不一样,同一段候选内容对不同租户的相关性排序可能完全不同。

UniRAG 允许多个重排序模型同时启用,按租户配置路由。默认租户可以共用同一个通用重排序模型,但对检索质量要求高的租户,可以单独部署一个基于行业数据微调过的重排序模型。配置方式仍然是一段路由规则。这样做的成本收益非常清晰:通用模型保障底线,专用模型提升上限,而这两者之间不需要改一行检索代码。

另外还做了一个"租户级语义缓存"。同一租户的相似问题在短时间内会被大量重复提问(典型如企业内部制度问答),如果每次都走完整四路召回和重排序,资源浪费很大。UniRAG 在缓存设计上做了两层:第一层是租户内常见问题的完全匹配缓存,命中直接返回;第二层是语义缓存,用 embedding 相似度判断两个问题是否等价,相似度超过阈值就直接复用之前的回答。这个设计使某些高频租户的问答延迟从 4 秒降到 0.5 秒以内,效果非常立竿见影。

5. 部署上线与压测复盘:从 Mac 本地到线上稳定

理论设计再多,最后都要落到部署这一步。UniRAG 在整个开发过程中的部署迭代对我们帮助很大,这里把从本地到线上的完整路径和压测后的调整写出来,也算是最有实操参考价值的部分。

5.1 在 Mac 上先跑通单机版:依赖选择与快速启动

很多人问怎么在 Mac 上搭建 rag 知识库或者完整的 RAG 项目。如果你只是先跑通 UniRAG 的单机版做功能验证,依赖清单其实不复杂:一个向量数据库(我用的是单机模式跑在 Docker 里)、一个嵌入模型服务(本地加载开源 embedding 模型)、一个 LLM 推理服务(可以接入 OpenAI 兼容接口,也可以本地部署)、再加一个 Redis 做缓存和配额存储。

单机版我只在 Docker Compose 里编排向量库和 Redis,模型服务直接跑在宿主机上,方便调试。这个阶段最重要的不是性能,而是把租户路由逻辑调通。我建议第一次跑通时只创建一个测试租户,上传一份文档,跑通"文档加载 → 切片 → 向量化 → 检索 → 生成"的最小闭环,再逐步加第二、第三个租户测试隔离性。

5.2 线上部署的细粒度控制:索引更新、缓存与并发限流

从单机版到线上部署,最大的变化是要考虑多租户的并发争抢。线上我做了三个层面的控制。

索引更新控制:上面提过,每个租户的索引任务走独立队列,这里再补充一个细节——队列的消费者数量按租户配额动态调整。大租户可以配置 4 个消费者同步处理,小租户 1 个就够,避免后台任务占用过多 CPU 和内存。

缓存控制:语义缓存按租户分片存储,过期策略也按租户配置。比如企业制度问答类租户,缓存 TTL 设 24 小时;金融数据类租户,数据更新频繁,缓存 TTL 缩短到 15 分钟并且监听数据更新事件主动失效。

并发限流:网关层统一做租户级限流,采用令牌桶算法。这里有个关键经验——限流一定要按租户的配额做,而不能只看全局 QPS。只看全局的话,某个租户的突发流量会把其他租户的请求全部挤掉。UniRAG 的限流方案是:全局 QPS 限制兜底,租户级 QPS 限制独立控制,租户超限时返回特定的限流错误码,让前端可以提示"当前知识库访问繁忙"。

5.3 压测后的三个调整:索引重建、缓存失效与租户级超时

压测阶段我们暴露了三个问题,每个都值得展开。

第一个问题是索引长时间运行后性能衰减。压测跑了一周,某大租户的向量 collection 写入量很大,检索延迟从 200ms 涨到 800ms。排查后发现是向量索引的段合并没有跟上写入速度。解决方式不是调段合并参数,而是设计了一个定时索引重建机制,每天凌晨对数据量大、写入频繁的租户执行一次索引优化。这里我给新手一个建议:别等问题出现才去看索引性能,要按租户的数据增量做索引健康度监控。

第二个问题是缓存失效风暴。有一次某个租户批量更新了知识库,触发了大量缓存失效,瞬间后端查询压力暴增,导致一串请求超时。这个问题的根因是缓存失效没有做错峰。修改后的方案是:缓存失效通知先发到消息队列,由消费者分批、错峰执行失效操作,同一租户的失效操作串行执行,绝不允许所有缓存同时清空。

第三个问题是租户级超时设置。最开始所有租户的检索超时都是同一个值,结果一个小租户的图谱查询因为数据量小反而更容易超时——倒不是性能问题,而是图谱子图查询的冷启动延迟。后来我把超时改为"基础超时 + 租户数据量系数"的公式计算,并且给每个租户单独配置超时阈值。这个公式比较简单直接:

timeout_ms = 800 + min(2000, tenant_doc_count / 10000 * 200)

这条公式的意思是:租户文档量越大,给的超时时间越多,但最高不超过 2800ms。这个经验性公式未必普适,但它的思路是对的——多租户系统的超时配置不能一刀切,要跟着租户规模走。

6. 说点个人项目体会

最后分享几个我做 UniRAG 这个项目过程中反复验证过的体会。

第一个体会是多租户设计永远要先想清楚"隔离的底线在哪"。如果租户数据泄露是零容忍的,那么存储层的物理隔离就不能省;如果想降低成本必须在共享池上做文章,那就必须在网关和检索链路里把权限注入做扎实。这两个方向没有对错,但不能模棱两可、两头都想要。

第二个体会是混合检索是个"慢工出细活"的工程。不要指望四路召回一次就能调好,我在项目里花了大量时间做的是按租户、按问题类型去对比不同召回路数的效果。后来我把对比过程做成了可视化工具,每次调整后能直接看到不同租户的指标变化,效率才提上来。

第三个体会是 RAG 项目的扩展性比想象中重要。今天你只做文档问答,明天客户就会要结构化数据查询,后天就会提单子图推理。如果你一开始的知识库抽象层设计得好,后面接新数据源就是写一个适配器的事;如果当初图省事把所有知识都塞进向量库,后面重构的代价会大得多。

对于想跑通类似多租户 RAG 项目的朋友,我的建议是:先用文档知识库跑通最小闭环,再逐步加结构化数据源,最后再上图谱。每加一类知识源,都要确保检索质量确实提升,而不是为了炫技增加复杂度。这个项目做下来我最大的体会是,多租户 RAG 的难点从来不在某个单点技术上,而在"隔离、共享、知识形态"这三个维度的平衡上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询