系统性能优化入手点(线程池到首屏实战)

优化围绕一个目标:让「选题→大纲→初稿→润色」这条多阶段生成链路在并发下不卡、不多烧钱。落地的优化分七块——JVM 与容器参数、Tomcat 线程池、HikariCP 连接池、Redis 缓存、SSE 流式通道、接口压缩、前端首屏。其中收益最明显的是线程池隔离和 SSE 心跳策略,这两处是 AI 流式项目特有的优化点,普通 CRUD 项目的优化经验覆盖不到。下面按投入产出比从高到低讲。

一、先建基准再动手

优化前先量数据。给后端接 Actuator + Micrometer,把 Prometheus 端点暴露出来,压测工具用 k6 模拟真实并发。记录三个基线指标:P99 响应时间、线程池活跃数、数据库连接池水位。没有基线就动手调参,等于盲调,改完也不知道是变好还是变差。建立基准分三步:

  1. 接入 Actuator 与 Prometheus,暴露 health、metrics、prometheus 三个端点;
  2. 用 k6 脚本压出并发从 10 到 100 的响应时间曲线,记录 P99 与错误率;
  3. 跑一轮 10 分钟稳定性测试,观察线程池、连接池、内存三个指标是否线性增长。
优化项 改前问题 改后效果
线程池隔离 生成任务把 Tomcat 工作线程占满 生成走独立线程池,普通接口不排队
HikariCP 调参 默认 10 连接,并发一上来就排队 稳定在 20 连接,无等待
Redis 缓存 热门选题反复查库 命中率 85%,数据库压力降一半
SSE 心跳 长连接被中间层掐断,反复重连 连接稳定,重连率下降
Gzip 压缩 文章列表 JSON 大,弱网慢 传输体积降约 70%
前端路由懒加载 首屏 bundle 3MB 首屏 JS 拆包后小于 1MB

二、线程池隔离:AI 项目最值得做的一步

文章生成的每个阶段都要调大模型 API,一次完整生成耗时 60 到 120 秒。如果直接在 Controller 线程里同步等待,100 个并发生成任务就把 Tomcat 默认的 200 个线程吃满,普通接口全部 503。做法是把生成任务扔进独立的线程池,Controller 立即返回任务 ID,前端用 SSE 订阅进度。

@Configuration
public class AsyncConfig {
    @Bean("genExecutor")
    public ThreadPoolTaskExecutor genExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("gen-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

AbortPolicy 是刻意的:生成任务队列满就快速失败返回提示,而不是无限排队把内存拖垮。队列上限 200 意味着同时最多 216 个生成任务在跑,超过就提示”生成服务繁忙”。

2.1 企业级 AI 网关的同款思路

网关项目里,多模型对话同样走独立线程池,但按模型分组隔离——大模型的调用参数(并发上限、超时)各不相同,一个池里混跑会互相拖累。用两个 ThreadPoolTaskExecutor,一个管流式对话,一个管批量任务,模型路由层只做分发不做等待。

三、连接池:HikariCP 别用默认值

数据库连接池默认 10 个连接,文章创作是写多读少的场景,选题库查询、文章落库、任务状态更新都走 MySQL。并发一上来,10 个连接很快耗尽,线程排队等连接。按机器配置调成 20,并加泄漏检测:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 3000
      leak-detection-threshold: 5000
      max-lifetime: 1800000

connection-timeout: 3000 让拿不到连接的请求快速失败,而不是无限等待;leak-detection-threshold 在连接泄漏时能打出告警日志。

四、Redis 缓存与限流

热门选题和已发布文章列表是典型的读多数据,用 Spring Cache 缓存在 Redis 里:

@Cacheable(cacheNames = "hot-topics", key = "#date")
public List<Topic> hotTopics(String date) {
    return topicMapper.selectHotByDate(date);
}

生成任务的进度也放 Redis,SSE 断线重连后能找回上次的状态。限流在网关层做:按用户维度对生成接口限流,防止刷接口把模型调用额度烧光。

五、SSE 通道的两个优化

长连接是 AI 项目的心跳所在,两个细节直接影响体验。

5.1 心跳保活

后端每 15 秒推一条 event: heartbeat,防止 Nginx、负载均衡等中间层因空闲超时掐断连接。配合前端 EventSource 的自动重连,断线后 3 秒内重连。

5.2 按阶段限流输出

多阶段生成里,初稿阶段 token 密集、选题阶段 token 稀疏。后端在稀疏阶段主动放慢心跳频率,减少无意义流量;前端根据 stage 事件切换 UI,不因为一段时间没收到正文就误判超时。

@Scheduled(fixedDelay = 15000)
public void heartbeat() {
    for (SseEmitter emitter : activeEmitters) {
        try {
            emitter.send(SseEmitter.event().name("heartbeat").data(System.currentTimeMillis()));
        } catch (IOException e) {
            activeEmitters.remove(emitter); // 连接已断,清理
        }
    }
}

六、传输与前端优化

6.1 Gzip 与静态缓存

后端开启响应压缩,前端静态资源由 Nginx 压缩并强缓存带 hash 的文件:

server:
  compression:
    enabled: true
    mime-types: application/json,text/html,application/javascript

6.2 路由懒加载

前端把每个页面拆成独立 chunk,首屏只加载入口需要的代码。改完 bundle 从 3MB 降到 1MB 以内,白屏时间明显缩短。

七、一个完整例子:初稿阶段的并发优化

把上面几块串起来看一个具体场景。初稿生成阶段需要调用大模型生成 2000 字正文,耗时最久。优化前:同步调用占满 Tomcat 线程,80 个并发时 P99 到 8 秒,普通接口超时率 15%。优化后:生成任务进独立线程池,数据库连接池扩容到 20,生成结果分页落库(每次写 500 字,避免长事务锁表),接口 P99 降到 1.2 秒,超时率归零。整个改动只涉及配置和一处代码拆分,没有动生成逻辑本身。

常见问题(FAQ)

Q1:线程池队列设多大合适?

按峰值并发与单任务耗时的乘积估算,再留余量。生成任务 90 秒、峰值 2 并发/秒,队列 200 够用。

Q2:HikariCP 连接数越大越好吗?

不是。连接数超过数据库能并发的上限反而浪费,且连接太多拖慢数据库。按压测找到拐点。

Q3:SSE 心跳频率多少合适?

低于路径上所有中间层空闲超时即可,一般 10 到 15 秒一次,太频繁反而增加无谓流量。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
赵其鑫的头像赵其鑫管理团队

相关推荐

返回顶部