一个真实项目的架构复盘:8 个月建设周期、850 万投资、百万级知识库、6 个微服务。 上线后 CPU/GPU 资源利用率从不足 30% 提升到 75% 以上,系统可用性达到 99.99%。 本文完整拆解四条主线:分层缓存、全链路异步、数据库治理、DDD 微服务拆分,以及落地过程中踩过的坑。
一、背景:AI 业务为什么把单体架构打爆了
企业智能知识库、AI 问答、文档智能解析这类功能,正在从"锦上添花"变成日常办公的刚需。但它们的流量特征与传统办公系统完全不同,原有架构在设计之初并没有考虑这些差异。
1.1 流量特征的根本变化
|
维度 |
传统 OA 系统 |
AI 办公业务 |
|
流量形态 |
平稳、可预测,集中在工作时段 |
突发性强,波动幅度大 |
|
单请求成本 |
毫秒级响应,CPU 占用低 |
秒级至分钟级,同时消耗 CPU 与 GPU |
|
峰值并发 |
相对温和 |
瞬时并发量高 |
|
任务时长分布 |
短且均匀 |
长尾明显,少数任务耗时极长 |
|
资源类型 |
以 CPU 与内存为主 |
CPU、内存、GPU 混合,且 GPU 成本最高 |
原有系统是一个典型的单体工程:用户权限管理、知识检索、文档处理、大模型推理、数据存储全部打包在一个应用里开发和部署。业务规模一上来,三个结构性缺陷立刻暴露。
1.2 三个结构性缺陷
第一,业务耦合度高,迭代与发布风险大。 模块之间依赖关系复杂,哪怕只调整一个很小的业务逻辑,也需要对整个系统重新打包发布。AI 业务的特点是需求更新快、试验多,这种发布粒度直接拖垮了迭代效率,而且每一次全量上线都是一次风险敞口。
第二,资源分配僵化,利用率不足 30%。 服务器 CPU、内存及 GPU 全部采用固定配置,无法随业务负载动态调整。业务高峰期,AI 推理与文档处理任务集中执行,造成请求堆积和接口超时;业务低峰期,部分算力资源长期闲置。这是最直接的浪费 —— GPU 是整套系统中最昂贵的资源,却有一多半时间在空转。
第三,容错能力弱,局部故障会放大成全局故障。 系统缺少完善的故障隔离、限流和熔断机制。当某个模块出现异常、慢查询或算力资源耗尽时,问题会通过线程池和连接池迅速传导,引发连锁故障,最终导致整个系统不可用。
1.3 改造目标
|
目标 |
衡量方式 |
|
承载突发流量 |
峰值并发下不出现请求堆积与接口超时 |
|
算力按需分配 |
资源利用率显著提升,GPU 不再长期空转 |
|
高可用 |
单个模块故障不影响整体服务可用性 |
|
快速迭代 |
支持单服务独立开发、测试与灰度发布 |
二、整体架构总览
改造后的架构基于云原生微服务体系,整体分为接入层、网关层、微服务层、中间件层以及存储与算力层。
图 1 智能知识管理平台整体架构
各层职责划分如下:
|
层次 |
主要组成 |
核心职责 |
|
接入层 |
客户端、Web、企业微信 |
多端统一接入 |
|
网关层 |
API 网关 |
身份认证、访问控制、限流、请求路由 |
|
微服务层 |
六个业务微服务 |
各自领域内的业务逻辑 |
|
中间件层 |
分层缓存、消息队列集群 |
降低后端压力、削峰解耦 |
|
存储与算力层 |
主从数据库、向量库、GPU 资源池 |
数据持久化与模型推理 |
下面按四条优化主线逐层展开。
三、第一层:分层缓存,把数据库和 GPU 从重复计算里解放出来
3.1 问题:重复请求是最大的隐性浪费
平台日常运行中存在大量重复的知识检索和智能问答请求。如果每一次请求都直接查询数据库或调用大模型进行推理,会带来三重代价:产生大量重复计算、拉长接口响应时间、消耗本应留给新请求的算力。
对于大模型推理而言,重复计算尤其昂贵 —— 同一个问题被问两遍,成本就是实实在在的两遍。
3.2 分层缓存结构设计
我设计了"本地缓存加分布式缓存"的分层架构,并配套建设热点数据预热机制。两层缓存的定位差异如下:
|
层级 |
部署位置 |
主要缓存内容 |
优势 |
局限 |
|
L1 本地缓存 |
各服务节点进程内 |
问答模板、系统基础配置、高频访问的轻量级知识数据 |
无需网络通信,访问速度最快 |
容量有限,多实例之间不共享 |
|
L2 分布式缓存 |
独立集群,主从架构并配置哨兵 |
热门问答结果、高频检索词条、用户会话等动态数据 |
多实例共享、容量大、支持故障自动切换 |
存在一次网络往返开销 |
分层的意义在于:把访问频率最高、体积最小的数据放在代价最低的位置,让绝大部分请求止步于进程内部;只有未被本地缓存覆盖的请求才会走到分布式缓存,从而显著降低网络与后端压力。
3.3 读取路径与命中策略
图 2 两级缓存读取路径
读取流程遵循 L1 到 L2 再到回源的顺序:请求先查询本地缓存,命中则直接返回;未命中则查询分布式缓存,命中后回填本地缓存;仍未命中时,先经布隆过滤器判断数据是否存在,确认可能存在后才获取分布式锁并回源查询,随后把结果同时写回两级缓存。
这里有一个容易被忽略的设计要点:回源阶段必须加分布式锁并做二次检查。热点数据失效的瞬间,可能有大量并发请求同时发现缓存未命中,如果全部放行到数据库或模型服务,缓存不但没有起到保护作用,反而制造了一次瞬时洪峰。
3.4 缓存三大经典风险的应对
|
风险类型 |
典型表现 |
应对措施 |
|
缓存穿透 |
请求大量本身不存在的数据,全部落到数据库 |
使用布隆过滤器前置拦截不存在的检索条件,直接返回空结果 |
|
缓存雪崩 |
大量缓存同一时间失效,请求集中回源 |
为缓存设置不同的过期时间并叠加随机偏移量,打散失效时刻 |
|
缓存击穿 |
单个热点数据失效瞬间被大量并发访问 |
用分布式锁控制缓存重建过程,重建完成后立即回填 |
需要特别说明布隆过滤器的边界:它存在误判率,但只会把不存在的数据误判为"可能存在",不会反向漏判。因此它适合做前置拦截,不能替代后续的真实查询校验;同时新增数据时必须同步写入过滤器,否则新知识会被错误拦截。
3.5 热点数据预热
结合企业办公系统的流量规律,在凌晨业务低峰期自动加载热点知识数据。这样做解决的是缓存冷启动问题 —— 如果不在低峰期提前把热点数据载入缓存,系统启动或缓存大面积失效后,就会立刻出现大量请求同时访问数据库的瞬时冲击。
预热策略需要与业务节奏对齐:预热时间应当根据业务起量时间倒推,而不是简单固定在某个凌晨时刻。
3.6 落地效果
数据库查询量与大模型重复推理次数明显下降,后端存储与算力服务的运行压力得到有效缓解。
四、第二层:全链路异步,让耗时任务不再占用接口线程
4.1 问题:长耗时任务挤占了核心业务的线程
文档批量解析、长文本内容生成、知识库索引更新,这类任务通常执行时间很长。如果采用同步处理方式,服务线程会长时间等待任务执行结果,直接造成三个后果:挤占核心问答业务的线程资源、造成请求堆积、整体响应变慢,严重时接口线程池会被打满。
问题的本质是资源错配:用户提交一个文档解析请求,真正需要的是"任务被可靠受理",而不是"立刻拿到解析结果"。同步处理把这两件事强行绑定在了一起。
4.2 异步化改造的整体流程
核心思路是把实时问答这类短时任务,与文档解析、内容生成这类耗时任务彻底解耦。
图 3 文档解析异步处理时序
改造后的流程是:系统先完成参数校验和任务登记,随后立即向用户返回任务受理结果,同时把任务消息投递到消息队列,由独立的消费者服务在后台异步处理。用户通过任务编号查询处理进度和最终结果。
用户侧的操作体验变化如下:
|
阶段 |
同步模式 |
异步模式 |
|
提交请求 |
阻塞等待任务执行完成 |
校验与登记后立即返回任务编号 |
|
用户感知 |
页面长时间无响应 |
立即收到"任务已受理"的反馈 |
|
结果获取 |
依赖同一次响应返回 |
按任务编号查询处理进度与结果 |
4.3 任务分级与资源隔离
为保障核心业务优先运行,我按照任务类型和处理优先级划分不同的消息主题,并配置差异化的消费者资源:
|
任务类型 |
优先级 |
消费者资源配置 |
典型场景 |
|
实时交互类 |
高 |
配置较多消费者实例 |
实时问答、交互式请求 |
|
批量处理类 |
中 |
常规消费者实例 |
文档解析、文字识别、文本切片 |
|
后台更新类 |
低 |
少量消费者实例,负载较低时集中处理 |
知识库索引构建、批量数据刷新 |
这样做的价值在于错峰调度:实时交互任务随时可以获得充足资源,而后台批量任务在系统负载较低时集中处理,把低谷期的闲置算力利用起来,同时避免与核心业务争抢资源。
4.4 可靠性保障机制
异步化最大的代价是可靠性下降 —— 消息一旦投递出去,就必须保证它最终被正确处理。为此配置了四层机制:
|
机制 |
作用 |
|
消息确认 |
消费者处理成功后提交消费位点,避免消息丢失 |
|
失败重试 |
因网络波动或临时服务异常而失败的任务,按预设规则自动重试 |
|
死信队列 |
超过最大重试次数的消息转入死信队列,由运维人员排查与补偿处理 |
|
消费幂等 |
以业务唯一标识实现去重,避免消息重复投递造成数据重复写入 |
消费幂等这一条尤其重要。消息队列在异常场景下可能重复投递同一条消息,如果没有幂等控制,同一份文档可能被解析两次、同一笔操作可能被记录两遍,事后排查非常困难。
4.5 落地效果
异步化改造完成后,耗时任务不再长期占用接口线程,核心问答服务的响应速度和系统整体吞吐能力均得到明显提升。
五、第三层:数据库架构优化,拆掉数据层的并发瓶颈
5.1 问题:数据层成为主要瓶颈
随着平台知识库数据量突破百万级,原有的单库单表架构逐渐出现三个典型症状:查询速度明显下降、读写竞争加剧、数据库连接被耗尽。数据层成了制约系统并发能力的主要瓶颈。
我从四个方面开展治理:索引与 SQL 优化、读写分离、分库分表、连接池参数优化。
5.2 索引与 SQL 治理
针对高频知识检索、用户问答记录查询、文档状态查询等业务场景建立联合索引,并依据数据库执行计划调整字段顺序。这里的关键认知是:联合索引的字段顺序直接决定它是否会被命中,字段顺序应当遵循等值条件在前、范围条件在后的原则。
同时做两件"减法"。一是清理长期未使用的冗余索引 —— 索引并非越多越好,每一个索引都会拖慢写入速度并占用额外存储。二是对慢查询逐条分析执行计划,消除全表扫描和不必要的回表操作。
5.3 读写分离与一致性取舍
系统采用一主多从架构:主库负责数据写入和更新,从库承担大部分查询请求。通过把读请求分散到多个从库,缓解主库的读写竞争,提高数据库整体并发处理能力。
读写分离必须明确一致性边界:
|
查询类型 |
路由目标 |
判断依据 |
|
普通列表查询、检索类查询 |
从库 |
读多写少,可容忍毫秒级的复制延迟 |
|
权限校验、额度扣减、写后立即读 |
主库 |
强一致性要求,避免因主从复制延迟读到旧数据 |
这条边界如果划错,会出现"数据明明保存成功了却查不到"的诡异问题,排查成本极高。
5.4 分库分表
针对数据量增长较快的知识文档、问答记录和操作日志,按知识分类、企业标识和创建时间等维度进行水平分片,降低单表数据规模,避免因单表数据量过大而导致查询性能持续下降。
|
数据对象 |
分片维度 |
目的 |
|
知识文档 |
企业标识与创建时间 |
降低单表规模,支撑按租户检索 |
|
问答记录 |
企业标识与创建时间 |
分摊高频写入与查询压力 |
|
操作日志 |
创建时间 |
便于按时间归档与冷热数据分离 |
分库分表不是"配置好就结束了",它会引入三个必须同步解决的衍生问题:
|
衍生问题 |
解决方案 |
|
全局唯一主键 |
采用分布式 ID 生成策略,替代数据库自增主键 |
|
跨分片查询 |
使分片键与核心查询条件对齐;无法对齐时通过映射表或搜索引擎承接 |
|
跨分片分页与聚合 |
避免跨分片关联查询,改由应用层二次聚合,或引入宽表承接复杂查询 |
5.5 连接池参数治理
这一步最容易被忽视,但收益最为直接。核心判断是:连接池不是越大越好。当连接数超过数据库的实际处理能力后,多出来的连接只会互相争抢 CPU 和锁资源,反而拉高响应时间。
|
参数 |
调整前 |
调整后 |
调整依据 |
|
最大连接数 |
100,各实例随意配置 |
20 |
参考"CPU 核数乘 2 加有效磁盘数"的经验值,并确保实例数乘单实例连接数不超过数据库上限 |
|
最小空闲连接数 |
与最大连接数差距较大 |
与最大连接数一致 |
避免连接频繁创建与销毁带来的性能抖动 |
|
连接超时时间 |
30 秒 |
3 秒 |
快速失败,防止线程在获取连接环节无限堆积 |
|
连接最大存活时间 |
30 分钟 |
25 分钟 |
必须短于数据库服务端的空闲超时时间,避免使用已被服务端关闭的连接 |
|
连接泄漏检测 |
未开启 |
10 秒 |
及时发现连接泄漏并触发告警 |
其中"连接最大存活时间必须短于数据库服务端空闲超时"这一条,是被生产环境反复验证过的经验:否则应用会持续使用数据库已经关闭的连接,表现为难以复现的间歇性报错。
此外,还需要对异常连接和长时间占用的连接进行监控与回收,防止高并发场景下连接资源被耗尽。
5.6 落地效果
数据库查询效率和并发处理能力明显提升,数据层不再成为系统性能扩展的主要瓶颈。
六、第四层:基于领域驱动设计的微服务拆分
6.1 问题:单体架构下的资源竞争
原有单体架构中各类业务模块集中部署,存在两个结构性缺陷。
一是资源竞争。当模型推理或文档解析任务大量执行时,会占用大量 CPU、内存和线程资源,进而影响用户登录、权限校验和知识检索等基础业务。
二是无法独立扩容。整个应用只能整体扩容,热点模块拿不到额外资源,冷门模块却跟着一起浪费。
6.2 服务边界划分
基于领域驱动设计思想,按照业务职责和领域边界,将原有单体系统拆分为六个微服务:
|
微服务 |
核心职责 |
资源特征 |
扩容策略 |
|
用户权限服务 |
登录、鉴权、权限模型管理 |
CPU 占用低 |
固定少量实例 |
|
知识检索服务 |
向量检索与全文检索、召回排序 |
内存与 IO 密集 |
随查询量扩缩 |
|
智能问答服务 |
请求编排、上下文管理与结果整合 |
CPU 占用中等 |
随并发量扩缩 |
|
文档解析服务 |
文档解析、文字识别、文本切片 |
CPU 密集 |
随消息积压量扩缩 |
|
模型推理服务 |
大模型推理计算 |
GPU 密集 |
专用 GPU 节点,显存池化调度 |
|
系统运维服务 |
任务调度、监控告警与日志管理 |
CPU 占用低 |
固定少量实例 |
各微服务职责相对独立,可以分别进行开发、测试、部署和扩容。服务之间根据业务实时性要求,通过接口调用或消息队列进行通信,降低模块之间的直接依赖。选型原则是:
- 强实时、需要立即拿到结果的场景,走同步接口调用,例如权限校验;
- 可延迟、耗时、允许最终一致的场景,走消息队列异步处理,例如索引更新。
6.3 算力隔离:把模型推理单独拎出来
这是整套拆分中最关键的一刀。算力消耗最大的模型推理服务被独立拆分,并配置专用 GPU 资源。
拆分之后职责变得清晰:智能问答服务只负责请求编排、上下文管理和结果整合,具体的模型计算任务全部下沉到模型推理服务完成,普通业务服务与 AI 推理任务不再相互争抢资源。
这样带来的直接收益是故障域收敛:文档解析把 CPU 跑满时,用户登录、权限校验、知识检索依然稳定;推理服务实例异常时,问答功能降级,其他业务完全不受影响。
6.4 网关、服务发现与故障摘除
- 服务注册与发现:实现服务动态寻址,实例的上线与下线能够被自动感知。
- 网关统一入口:集中处理身份认证、访问控制、限流和请求路由。限流放在网关层执行,可以拦截绝大部分无效流量,避免它们穿透到业务层消耗资源。
- 故障摘除:当某个服务实例出现异常时,注册中心及时将其从可用实例列表中移除,后续请求不再被路由到故障节点。
- 熔断降级:配合熔断策略,当依赖服务的错误率超过阈值时自动熔断并返回兜底结果,而不是让调用方线程继续堆积。
6.5 弹性扩容
完成微服务拆分后,各业务模块可以根据实际负载情况独立扩容。例如在员工集中使用 AI 问答功能时,只需单独增加智能问答服务和模型推理服务的实例,完全不需要扩用户权限服务和系统运维服务,资源利用效率显著提高。
这里有一个容易做错的点:扩容指标要与业务形态匹配。面向查询的服务适合按请求量扩缩,而像文档解析这类流量由队列堆积决定的服务,更适合按消息积压量扩缩 —— 否则会出现请求量不高但队列已经堆积严重、系统却不扩容的情况。
6.6 落地效果
资源利用率和系统扩展能力显著提升,不同业务模块之间实现了真正的隔离与独立伸缩。
七、上线效果
四层优化叠加后的整体收益:
|
维度 |
改造前 |
改造后 |
|
服务器与 GPU 资源利用率 |
不足 30% |
75% 以上 |
|
系统整体可用性 |
高峰期偶发整体不可用 |
99.99% |
|
峰值并发承载 |
请求堆积、接口超时 |
稳定支撑业务常态化运行 |
|
服务发布粒度 |
单体全量打包发布 |
单服务独立开发、测试与灰度发布 |
|
故障影响范围 |
单模块异常引发连锁故障 |
故障被隔离在单个服务之内 |
说明:资源利用率与可用性为项目上线后稳定期的观测值;其余维度为架构改造带来的方向性变化,具体数值与部署规模相关。
八、踩坑复盘:五个必须提前知道的问题
上面所有方案看起来都很"标准",但真正让人加班的,往往是下面这些细节。这一节也是全文最有价值的部分。
坑一:分片键选错,跨分片查询把性能吃光了
最初按文档编号作为分片键,结果业务使用频率最高的"按企业加时间维度查询文档列表"变成了全分片扫描 —— 所有分片表都要扫一遍再聚合,性能比不分片还差。
修正思路:分片键必须与最核心的查询条件对齐,改为按企业标识分库、按创建月份分表,并对无法对齐的查询引入映射表和搜索引擎承接。
经验总结:分库分表的收益完全取决于分片键的选择,选错就是纯粹的负收益。
坑二:缓存预热的时间点拍错,等于没预热
预热任务最初设定在凌晨三点半执行,但业务量要到早上七点才开始起量。预热数据在低峰期写入,缓存过期时间又设得较短,等到早高峰时早已失效。
修正思路:预热时间应当根据"业务起量时间减去缓存有效期"倒推,同时延长热点数据的有效期,并用业务侧埋点动态刷新热点数据,而不是固定刷新同一批数据。
坑三:本地缓存多实例不一致
多实例部署场景下,各节点的本地缓存各自为政。某个实例更新了数据,其他实例的本地缓存仍是旧值,用户感知为"刷新了页面数据却没变"。
修正思路:一是严格控制本地缓存的有效期,把不一致窗口压缩到业务可接受范围;二是通过消息广播机制通知各实例主动清除对应的本地缓存。
经验总结:使用本地缓存就必须接受"最终一致",并且要事先明确这个窗口的业务可容忍上限。
坑四:消息重复消费,根源是 AI 任务耗时太长
AI 文档解析单次可能运行数分钟,超过了消息队列对单次消费耗时的容忍上限,消费者被踢出消费组并触发再平衡,消息被重新投递给另一个实例,导致同一份文档被解析两遍、数据重复写入。
修正思路:一是延长单次消费的容忍时长并降低单批拉取数量;二是把耗时任务改为"消费即确认加任务状态机驱动"的模式,让消息队列只负责触发,不承担长任务的生命周期管理;三是全链路补齐基于业务唯一标识的幂等控制。
经验总结:消息队列并不适合承接长耗时任务。长任务应当只把消息当作"启动信号",真正的状态流转由业务表和状态机承载。
坑五:异步改造完成后,用户以为系统挂了
改为异步之后线程不再阻塞,但用户提交完只能看到一个任务编号,没有任何进度反馈。业务方投诉"上传了文档系统就没反应了"。
修正思路:补齐任务状态流转机制,从待处理、处理中到成功或失败,并提供进度上报和前端进度展示。
经验总结:异步化的技术收益和体验收益是两回事。技术上确实不阻塞了,但如果不做进度可观测,用户感受到的反而更差。
九、总结与适用边界
本次架构重构通过分层缓存优化、异步任务改造、数据库性能治理和微服务领域拆分,解决了传统单体架构难以适配 AI 高并发业务的问题。但比具体方案更重要的,是几点方法论层面的认识。
第一,先定位瓶颈,再选择技术。 缓存、消息队列、微服务都不是银弹。如果不先测出真实瓶颈在哪一层就全套上齐,只会让系统复杂度暴涨,而收益相当有限。
第二,高并发治理是系统性的。 需要同时分析系统调用链路、任务执行方式、数据访问模式和资源使用情况,四个维度缺一不可。
第三,每一步都要在稳定性、一致性和成本之间做取舍。 读写分离会引入主从延迟,分库分表会带来跨分片查询,异步化会降低可靠性保障 —— 收益和代价永远同时到来,没有只拿好处不付代价的方案。
第四,技术方案必须能被业务感知。 异步任务不做进度反馈、缓存不一致没有兜底策略,架构再漂亮用户也不买账。
适用边界
这套方案并非普适。如果系统的日均请求量只有几百,也没有算力密集的长耗时任务,那么直接上微服务加分库分表是负收益 —— 由此带来的运维复杂度和分布式一致性问题,远比原本的性能瓶颈更麻烦。
判断标准其实很简单:先测出真实瓶颈在哪一层,再决定要不要动那一层。 如果 CPU 和数据库连接数都很健康,只是某一条慢查询拖了后腿,那你需要的是一条索引,而不是一套微服务。
未来我会继续深入云原生与分布式高并发方向,进一步优化 AI 系统的资源调度、故障隔离和弹性扩容能力,为企业智能化业务的稳定落地提供更加扎实的技术支撑。
如果这篇文章对你有帮助,欢迎点赞收藏。关于分层缓存、异步任务处理、分库分表的分片键设计,有想深入讨论的欢迎在评论区交流。





