第二章 常见密文检索系统架构

做密文检索查询排查,第一件事不是看日志,而是搞清楚你的系统架构长什么样。我自己排查过的案例里,至少有三分之一的问题根源出在架构选型和部署方式上。你想想看,单机、分布、混合,每种架构的故障模式完全不同。这一章我就带你把它们掰开揉碎了讲清楚。

2.1 单机架构

单机架构,说白了就是所有组件跑在一台机器上。索引、检索、加解密引擎全在一个进程或同一节点内。我刚开始做密文检索项目时,团队图省事直接上了单机。嗯,开发阶段确实快,但一上线就发现问题了——查询压力稍微上来,CPU 和内存抢得厉害。

单机架构的核心特征是:

  • 所有数据驻留本地磁盘或内存
  • 查询请求由单进程处理,无网络开销
  • 扩展方式只能是垂直扩(加 CPU、加内存)

适合什么场景?数据量在百万级以下、查询 QPS 低于 100、对可用性要求不高的内网工具或测试环境。我曾经见过一个内部审计系统,密文条目就几十万条,单机跑了三年没出过大问题。但一旦数据量破千万,或者查询并发超过 200,单机就容易成为瓶颈。

避坑提醒:单机架构最常见的失败原因并不是性能不够,而是索引和加密引擎共用一个内存池。我曾遇到一次,检索进程把堆内存占满,加密引擎写日志时直接 OOM,整台机器卡死。排查时发现日志里全是"OutOfMemoryError",但检索线程还在正常返回结果——诡异吧?所以单机下一定要做资源隔离,或者至少给 JVM 设置独立堆大小。

2.2 分布式架构

当数据量跨过千万级,或者查询 QPS 要求上千时,单机就扛不住了。这时候分布式架构是必然选择。分布式密文检索系统一般会分成几个角色:协调节点、数据节点、加解密服务节点,有时还会单独配一个元数据管理节点。

我参与过一个金融风控项目,密文指纹库接近 5 亿条,用了 20 台数据节点做分片。检索时先由协调节点做路由,把查询分发到对应分片,各分片并行检索,最后聚合结果再解密返回。整体设计有点像 Elasticsearch,但多了一层统一的加解密网关。

分布式架构的常见模式有:

  1. 主从复制:一个主节点写,多个从节点读。适合读多写少的场景。
  2. 分片(Sharding):数据按某个分片键(如用户 ID 的哈希)分散到不同节点,每个节点只存储部分数据。这是目前最主流的方式。
  3. 无中心对等网络(Gossip):所有节点平等,通过一致性哈希确定数据归属,适合动态扩缩容。

但分布式不是银弹。网络分区、节点宕机、数据倾斜,这些问题我在项目里都踩过。有一次分片哈希算法写错了,导致 80% 的数据落在同一个节点上,其他节点空闲。查询时那个热点节点 CPU 直接 100%,检索超时率飙到 60%。排查了整整两天才发现是分片键选择不当。

核心结论:分布式架构更适合数据量大、高并发、要求高可用的生产环境。但需要额外处理数据一致性、节点间通信开销、以及故障转移逻辑。如果业务对延迟敏感(要求 P99 < 50ms),分布式网络延迟可能成为新瓶颈。
我个人习惯:在分布式环境中,我通常会先在预发环境压测一次全量分片查询,观察节点间的数据分布是否均匀。可以用 SELECT count(*) FROM shard_table GROUP BY shard_id 类似的语句检查分片数据量差异。如果最大分片 / 最小分片 > 2,说明分片键有问题,赶紧调整。

2.3 混合部署方式

实际生产环境里,纯单机或纯分布式的情况都不多。更多是把两者结合起来——我称之为混合部署。举个例子:把核心密文索引放在分布式集群里,但把最近 30 天的高频检索缓存放在本地单机内存里。既享受分布式的扩展性,又利用单机的低延迟。

另一种常见混合方式是:读写分离 + 本地缓存。写入走分布式主节点,保证数据一致;读取先查本地内存缓存(单机),缓存未命中再走分布式查询,然后回填缓存。我在一个电商平台的密文订单检索系统里就这么干过——查询延迟从平均 15ms 降到了 1.2ms,缓存命中率做到了 85%。

混合部署的典型拓扑有:

  • 分片集群 + 本地代理缓存:每台应用服务器部署一个检索代理,代理维护一个 LRU 缓存,命中则直接返回,未命中转发到分布式集群。
  • 热温冷分层存储:热数据(最近 7 天)存在本地 SSD 单机上,温数据(7~30 天)存在分布式集群,冷数据(30 天以上)放到对象存储(如 S3),查询时依次回源。
  • 边缘节点 + 中心集群:把高频检索的密文索引下发到用户附近的边缘节点(类似 CDN),边缘节点独立处理大部分查询,中心集群聚合低频查询和全量写入。

混合部署的挑战在于一致性维护。本地缓存过期策略、分层之间的迁移逻辑、边缘节点与中心节点的版本同步,这些一旦没处理好,就会出现「同一个密文在不同节点查到不同结果」的诡异问题。我亲自处理过一个 case:边缘节点的缓存 TTL 设了 10 分钟,但中心集群已经更新了密文,导致边缘节点连续返回旧值,业务方判定为「查询结果错误」。最后我们加了版本号校验和主动失效通知才解决。

维度 单机架构 分布式架构 混合部署
数据量 百万级以下 千万级~百亿级 任意量级,按分层管理
查询延迟 低(<1ms 本地) 中(1~20ms 含网络) 极低(热数据 + 缓存)
可用性 低(单点故障) 高(多副本 / 故障转移) 较高(缓存 + 降级)
运维复杂度 低 高(节点管理、协调) 中等(缓存一致性需额外工作)
典型失败原因 OOM、磁盘 IO 瓶颈 网络分区、数据倾斜 缓存与中心数据不一致
我曾经踩过的坑:一个混合部署项目里,为了追求低延迟,我把本地缓存的大小设成了 2GB,结果因为未配置显式过期策略,缓存不断膨胀,最终触发了 G1GC 的 Full GC,导致应用线程暂停了 8 秒。那几秒里所有查询都超时了。后来我改为使用 Caffeine 缓存并设置 expireAfterWrite(5, TimeUnit.MINUTES) 和最大条目限制,才稳定下来。
密文检索系统三种架构对比 单机架构 检索进程 加解密引擎 本地索引存储 日志 / 元数据 分布式架构 协调节点 (Router) 数据节点1 数据节点2 加解密服务集群 元数据管理 (ZK/Etcd) 混合部署 本地代理 + LRU缓存 分布式集群(分片) 边缘节点(A) 边缘节点(B) 中心集群(全量写入) 说明:三种架构可相互过渡,混合部署是实际生产中最灵活的选择,但也最考验工程能力。

对了,最后提醒一句:架构没有银弹。单机简单但脆弱,分布式灵活但复杂,混合看似完美却引入一致性难题。选型时先问自己三个问题:数据量未来半年会增长到多少?查询延迟的 SLA 是多少?团队有多少运维人力?答案清楚了,架构自然就有了。