2. 现有架构分析:单体报表导出系统的痛点
在进入重构设计之前,必须对当前的单体报表导出系统进行彻底的解剖。这套系统在业务初期支撑了日均数万次的导出请求,但随着数据量增长与并发场景复杂化,其架构缺陷已成为稳定性与效率的瓶颈。以下从调用链路、资源竞争、故障隔离三个维度展开分析。
2.1 当前系统调用链路
单体报表导出系统的请求处理流程可概括为“同步串行”模式。用户发起导出请求后,API 网关将请求转发至单体应用,应用内部依次经过权限校验、SQL 组装、数据库查询、内存聚合、文件生成、上传对象存储、返回下载链接等步骤。整个过程在同一个线程内同步阻塞执行,直到文件生成完毕才释放连接。
2.2 同步阻塞:线程即资源,等待即浪费
在单体架构中,每个导出请求会占用一个 Tomcat 线程(或 Jetty 线程)直到文件生成完毕。对于一张包含 50 万行数据的报表,数据库查询可能耗时 8~15 秒,文件生成(Excel/CSV)再耗时 5~10 秒,整个请求周期长达 20 秒以上。在此期间,该线程无法被回收用于处理其他请求。
| 阶段 | 耗时占比 | 线程状态 | 资源占用 |
|---|---|---|---|
| 权限校验 + SQL 组装 | ~5% | 运行中 | CPU 低 |
| 数据库查询(大结果集) | ~50% | 阻塞在 JDBC 等待 | 数据库连接 + 线程挂起 |
| 内存聚合 + 文件生成 | ~35% | 运行中(CPU 密集) | 堆内存 + CPU |
| 上传对象存储 | ~10% | 阻塞在 I/O | 网络带宽 + 线程挂起 |
关键指标: 假设线程池最大 200,平均请求耗时 20 秒,则系统最大并发导出能力仅为 200 / 20 = 10 QPS(吞吐量)。当瞬时请求超过 10 个,后续请求全部排队等待,响应时间线性恶化。
2.3 资源争抢:大查询与小请求的“饥饿”冲突
单体系统内所有导出任务共享同一个数据库连接池和线程池。当某个用户导出“年度销售明细”(百万级数据量)时,该查询会长时间占用数据库连接,导致其他用户的“今日订单摘要”等轻量级导出请求因拿不到连接而超时。更严重的是,大结果集在内存中聚合时容易触发频繁 GC,甚至 Full GC,进而影响整个 JVM 的稳定性。
- 数据库连接池耗尽: 一个慢查询占用连接 15 秒,连接池(通常 20~50 个)迅速被占满,新请求直接报错“Cannot get connection”。
- 线程池阻塞: 所有工作线程被慢导出任务占据,健康检查、管理接口等轻量请求也无法获得调度。
- 内存抖动: 多个大结果集同时驻留在堆内存中,导致 GC 频率从每分钟 1 次飙升到每 10 秒 1 次,应用响应能力断崖式下降。
2.4 单点故障:进程崩溃 = 全部任务丢失
单体应用是一个“All-in-One”进程。一旦该进程因 OOM、慢查询死锁、磁盘写满等原因崩溃,所有正在执行中的导出任务将立即中断,已生成但尚未返回下载链接的文件也会丢失。由于没有任务持久化机制,用户只能重新发起导出请求,且无法获知失败原因。
生产案例: 某次双十一大促期间,报表导出模块因一个慢查询导致数据库连接池耗尽,进而触发应用线程阻塞,最终 JVM 发生 OOM 崩溃。恢复后,积压的 300+ 导出任务全部丢失,运营团队手动补跑了近 2 小时,严重影响大促数据决策时效。
2.5 痛点总结与重构驱动力
综合以上分析,单体报表导出系统的核心痛点可归纳为以下表格,这些痛点直接驱动了向 gRPC + 消费组架构的重构决策。
| 痛点 | 技术表现 | 业务影响 | 重构目标 |
|---|---|---|---|
| 同步阻塞 | 线程在 I/O 和计算期间挂起,无法复用 | 并发能力低,响应时间随请求数线性增长 | 异步化 + 任务队列 |
| 资源争抢 | 大查询占满连接池和线程池,小请求饥饿 | 服务质量不稳定,关键用户导出超时 | 资源隔离 + 消费组分流 |
| 单点故障 | 进程崩溃导致所有任务丢失 | 数据延迟,用户需重试,信任度下降 | 无状态化 + 任务持久化 |
| 扩展性差 | 只能垂直扩容,无法水平拆分 | 硬件成本高,扩容周期长 | 微服务化 + gRPC 通信 |