我们把 AI 应用跑在多台机器上:一台建索引、一台跑模型、一台做语音、几台跑数字人。这些机器需要看同一份知识——经验文档、运维档案、agent 的开工须知。单机时代这不算问题,人记得住文件在哪;机器一多,它就变成一个纯粹的数据流问题。
这篇文章不讲概念,只讲一件事:我们自建集群的共享知识库长什么样、怎么保证每台机器都能查到、以及五类不报错但致命的故障是怎么被抓住的。所有数字来自我们的运维档案,不是推演。
最反直觉的一条经验:知识库不能有"两个都算数的地方"。只要允许两边都能改,一场冲突迟早会把文档写坏。我们的做法是把链路拆成三条,各管一件事:
主控机 A 权威树(唯一可写源头) ↕ 每日一次双向 hash 合并(仅限日志型文档) 主控机 B 镜像(发给节点前的最后一站) │ 每 30 分钟:下发 ▼ Node1 NFS 服务端 ├── docs/ 主控机下发的只读镜像 ← 下发目标 └── inbox/ node1..node5 写入口(每人一格) ← 回流来源 │ 每 30 分钟:回流 ▲ Node2~5 挂载同一份,只读查 + 只写自己的格子
三条链路的职责必须分清,混在一起就是事故源头:
docs/ → 全集群 NFS 可见。这条路上的文件是只读镜像,谁都不许就地改,因为下一轮同步会覆盖。inbox/<自己那格>,主控机定时拉回权威树。inbox/ 不在下发范围内,只在 Node1 自己的磁盘上。mount 输出判定——那里永远看不到它。同步脚本写起来半小时,验证它有没有坏掉才是长期成本。我们现在每次巡检跑一份 12 项矩阵,逐节点执行同一组检查:
| # | 检查项 | 判据 |
|---|---|---|
| 1 | 连通与身份 | hostname + whoami 一致 |
| 2 | 知识库真的挂上了吗 | 客户端判挂载类型含 nfs;Node1 例外,判软链指向 |
| 3 | 入口文档可读 | 字节数与首行标记符合预期 |
| 4 | 文档内引用的路径逐个存在 | 9 条引用全部 test -e 通过 |
| 5 | 文档总量 | 文件计数 ≈ 950 份量级 |
| 6 | 检索实测 | 按关键词反查命中数 > 0(不只看文件在不在) |
| 7 | 写入口完整 | 五个节点格子齐全 |
| 8 | 写权限 | 在自己格子里建文件→删除 通过 |
| 9 | 真实写入落盘 | 真传一个经验文件(不是 touch 空文件) |
| 10–11 | agent 注入文档 | 开工须知存在且指向的知识库路径正确 |
| 12 | 运行中服务 | 检索/模型/语音等关键服务在跑 |
为什么第 6 项不是"检查文件在不在"就够?因为"文件在"和"查得到"是两回事:索引服务可能正在重建、或者挂了某个后端。只验文件等于只验了一半。
这 12 项跑完还要做一次全链路闭环:五个节点各写一条 → 主控机拉回五条 → 再下发 → 复查每个节点都能看到五条。只看写入成功不算通过,必须看到它绕一圈回到自己眼前。
同步脚本崩了反而好办——有报错就有方向。以下是真实拖住我们的五类问题,共同点:日志看起来一切正常。
回流脚本的过滤规则里写着"跳过 _ 或 . 开头的文件"——这条规则本身没错,它是留给模板和归档目录的。问题是自检时我们刚好把测试文件命名成带下划线前缀,于是:文件确实写进了 inbox/,回流程序输出"拉回:0 / 无新经验",不报错、不告警,文件永远不会回来。
某台机器重启后网络还没就绪,systemd 挂载单元启动失败。之后这台机器上所有 /mnt/... 路径都查不到文件——但服务照常跑、SSH 照常连,只是它读的是一个空目录。同一台机器上的其他检查项全绿。
判据很简单也很反直觉:findmnt 查该路径,返回根目录而不是 nfs,就是没挂上。修法是重载 + 重置失败状态 + 重新挂载;根因治理则是在挂载配置里加上"开机网络未就绪也不阻塞、超时可控"的参数,避免它每次重启都掉。
这条最伤。"写本机文件"≠"集群可见":本机有一份权威文档,下发源是另一份拷贝,只有下发源才会被推给节点。曾经有一次全集群都查不到刚写好的内容——因为那一步复制是纯手工的,没有自动化,漏了就等于全集群失明。
修法是把"本机权威 → 下发源"这一步补进每 30 分钟的定时任务里。但定时任务最长有 30 分钟延迟,所以重要更新当场手动复制一次更保险。
双向合并对追加型日志很合适:两边各加几行,取并集去重保序,结果正确。但版本化文档是整篇覆写的——一旦走行级并集,就会产出新旧内容混排加大量重复行。我们的一次事故:一份 55KB 的档案文档,合并后涨到 56KB、608 行,其中236 行是冗余。
结论:同步策略要按文档类型分流。追加型日志走行级合并;整篇覆写型文档走单向覆盖,一个字都不要让自动合并去猜。
脚本里带了 emoji 输出,在 GBK 控制台下直接抛编码异常 → 进程非零退出。任务"在跑",但每次都没跑成。修法是脚本内强制把标准输出重配为 UTF-8,并在批处理入口设定代码页与 UTF-8 环境变量。跨平台脚本里不要依赖终端默认编码。
知识库同步好了,还得查得到。我们的检索服务曾长期表现为"搜不出来",最初的判断是"模型不行",最后发现是三个纯工程缺陷叠加:
| 缺陷 | 后果 |
|---|---|
| 缓存判据只看文件清单,清单一变就全量重嵌 | 知识库每天都在加文档 → 每次都是全量。按当时的 CPU 嵌入速度约 32 秒/块,全量 3053 块 ≈ 27.1 小时 |
| 把整个嵌入循环包在锁里 | 重建期间所有查询等锁 → 请求超时 → 用户看到的是"检索返回空",不是"服务忙" |
| 用文件修改时间探测触发重建,而重建期间时间戳不变 | 每次查询都再起一个重建线程,互相踩踏 |
修法对应三条:按正文指纹增量复用已有向量、锁只持一瞬、加互斥标记并让关键词检索先发布可查。效果:体量没变,重建从 27.1 小时降到 1.9 小时(实测只需重嵌 209 块,其余复用),并且边建边可查,查询秒级恢复。
之后再动硬件:把嵌入后端从 CPU 挪到一台带独立显卡的节点,单块耗时从 32 秒 → 0.028 秒(约 1140 倍),检索响应从 30 秒以上 → 0.05 秒。前提是两边的嵌入模型摘要一致,旧向量才能原地复用、无需重嵌。
ls 有输出不等于可用。它不是一份漂亮架构图,而是一份踩过坑的清单。如果你正在把 AI 能力从"一台机器上的 demo"推向"多台机器上的服务",这些故障你迟早会遇到;区别只是你花三天还是三小时定位。
我们做 AI 落地服务时,集群与知识库这一层通常是客户最容易低估的部分:模型调用能一周接上,但让多个 agent 长期、稳定地共享同一份事实,需要的是上面这些纪律而不是模型选型。如果你正卡在这一步,可以直接把问题描述发给我们。
聊聊你的 AI 集群 / 知识库落地问题 →单机时人能记住文件在哪;多机时每台机器各持一份副本,任何一次手动改动都可能只改到其中一台。同步一旦靠人记,就一定会漏,而漏掉的表现通常不是报错,而是某台机器上查不到——静默失效。所以先把权威源、下发路径、回流路径三者固定下来,再让同步自动化。
不报错的故障。真正烧时间的不是脚本崩了,而是它每天显示成功、实际什么都没传。我们遇到过三类:文件名以下划线开头被脚本按规则跳过、挂载单元在重启后静默失败、本机文件改了但下发源没更新。三者共同特征是没有异常日志,只有某台机器查不到。
因为服务端正常不代表客户端正常。挂载失败、只读镜像过期、agent 注入文件指向旧路径,都只在单个节点上表现为异常。所以自检必须是矩阵式的:每个节点逐项跑同一组检查,比的是各节点结果之间的差异,差异出现即定位到具体节点。
不能一刀切。行级并集对追加型日志有效,但对整篇覆写的版本化文档会产出大量重复行与新旧内容混排。我们的一次事故是一份 55KB 文档经行级并集后涨到 56KB、608 行、236 行冗余。正确做法是给覆写型文档单独走单向覆盖。
要,但要清楚瓶颈在哪。我们自建检索服务从"查不到"到"秒级响应"的转折点不是换模型,而是修三处工程缺陷:全量重嵌(27.1 小时)、持锁期间所有查询超时、每次查询都触发一次重建。修完耗时降到 1.9 小时,再把嵌入后端从 CPU 换到 GPU,单块从 32 秒降到 0.028 秒。
顺带说一句我们自己在用的"内容资产":多机知识库解决的是"经验不丢",但真正能直接变成产品的,是那些已经结构化的数据。我们把自己整理的 3550 条苏州话标准词库做成了可下载的数字商品(JSON + CSV 双格式,含字段说明与零依赖读取示例),¥99 起,可先下载 100 条免费样本确认格式;需要别的方言或行业词表也可以走定制入口。
查看《苏州话标准词典词库 3550 条》商品页 →