第二章 常见密文检索系统架构
做密文检索查询排查,第一件事不是看日志,而是搞清楚你的系统架构长什么样。我自己排查过的案例里,至少有三分之一的问题根源出在架构选型和部署方式上。你想想看,单机、分布、混合,每种架构的故障模式完全不同。这一章我就带你把它们掰开揉碎了讲清楚。
2.1 单机架构
单机架构,说白了就是所有组件跑在一台机器上。索引、检索、加解密引擎全在一个进程或同一节点内。我刚开始做密文检索项目时,团队图省事直接上了单机。嗯,开发阶段确实快,但一上线就发现问题了——查询压力稍微上来,CPU 和内存抢得厉害。
单机架构的核心特征是:
- 所有数据驻留本地磁盘或内存
- 查询请求由单进程处理,无网络开销
- 扩展方式只能是垂直扩(加 CPU、加内存)
适合什么场景?数据量在百万级以下、查询 QPS 低于 100、对可用性要求不高的内网工具或测试环境。我曾经见过一个内部审计系统,密文条目就几十万条,单机跑了三年没出过大问题。但一旦数据量破千万,或者查询并发超过 200,单机就容易成为瓶颈。
2.2 分布式架构
当数据量跨过千万级,或者查询 QPS 要求上千时,单机就扛不住了。这时候分布式架构是必然选择。分布式密文检索系统一般会分成几个角色:协调节点、数据节点、加解密服务节点,有时还会单独配一个元数据管理节点。
我参与过一个金融风控项目,密文指纹库接近 5 亿条,用了 20 台数据节点做分片。检索时先由协调节点做路由,把查询分发到对应分片,各分片并行检索,最后聚合结果再解密返回。整体设计有点像 Elasticsearch,但多了一层统一的加解密网关。
分布式架构的常见模式有:
- 主从复制:一个主节点写,多个从节点读。适合读多写少的场景。
- 分片(Sharding):数据按某个分片键(如用户 ID 的哈希)分散到不同节点,每个节点只存储部分数据。这是目前最主流的方式。
- 无中心对等网络(Gossip):所有节点平等,通过一致性哈希确定数据归属,适合动态扩缩容。
但分布式不是银弹。网络分区、节点宕机、数据倾斜,这些问题我在项目里都踩过。有一次分片哈希算法写错了,导致 80% 的数据落在同一个节点上,其他节点空闲。查询时那个热点节点 CPU 直接 100%,检索超时率飙到 60%。排查了整整两天才发现是分片键选择不当。
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 瓶颈 | 网络分区、数据倾斜 | 缓存与中心数据不一致 |
expireAfterWrite(5, TimeUnit.MINUTES) 和最大条目限制,才稳定下来。
对了,最后提醒一句:架构没有银弹。单机简单但脆弱,分布式灵活但复杂,混合看似完美却引入一致性难题。选型时先问自己三个问题:数据量未来半年会增长到多少?查询延迟的 SLA 是多少?团队有多少运维人力?答案清楚了,架构自然就有了。