跳到主要内容
📖 本章预览

本章为预览版本,展示部分核心内容。完整内容包含详细源码解析、实战代码和面试要点,加入知识星球即可解锁全部章节。

第25章 性能优化:AI 响应从 3 秒压到 500 毫秒

本章基于 Spring AI 1.1.2,所有优化策略均可在生产环境落地

25.1 先搞清楚:AI 应用的延迟到底花在哪

25.1.1 一次 AI 请求的时间分解

用户发起请求到收到完整回复,时间花在这几个阶段:

┌──────────────────────────────────────────────────────────────────┐
│ 一次 AI 请求的时间线 │
├──────┬──────────┬──────────┬──────────────────┬─────────────────┤
│网络RTT│ Advisor链 │ RAG检索 │ LLM推理(主要瓶颈) │ 后处理+网络RTT │
│ ~5ms │ ~10ms │ ~50ms │ 500ms ~ 30s │ ~5ms │
└──────┴──────────┴──────────┴──────────────────┴─────────────────┘

关键发现:
1. LLM 推理占 90%+ 的时间 → 你优化代码层再多,也绕不开模型本身的速度
2. LLM 延迟 = TTFT + 生成时间
- TTFT(Time To First Token):模型吐出第一个字的时间,通常 200ms~2s
- 生成时间:和输出长度成正比,每个 Token 约 20~50ms
3. Token 数量直接影响延迟和成本 → 输入越长,推理越慢,花钱越多

25.1.2 优化策略全景图

                        性能优化策略

┌───────────────────┼───────────────────┐
▼ ▼ ▼
减少调用次数 减少单次延迟 提升用户体验
┌─────────┐ ┌──────────────┐ ┌──────────┐
│精确缓存 │ │Prompt精简 │ │流式响应 │
│语义缓存 │ │模型选择 │ │SSE推送 │
│批量合并 │ │并行化 │ │渐进渲染 │
└─────────┘ │异步非阻塞 │ └──────────┘
└──────────────┘

核心原则:
- 能不调模型就不调(缓存)
- 必须调就让它快(精简Prompt、选对模型)
- 快不了就让用户感觉快(流式响应)

25.2 缓存:最暴力也最有效的优化

25.2.1 精确缓存

原理:相同输入 → 相同输出。对 input 做 MD5 作为 key,命中直接返回,零延迟。适合 FAQ、固定查询等确定性场景。

/**
* 精确缓存(25.2.1节)
*
* 原理:input 的 MD5 作为 Redis key,命中直接返回
* 适用场景:FAQ、固定查询、确定性问答
* 命中率:取决于用户输入的重复度,FAQ 场景可达 60%+
*
* 注意:temperature > 0 时,相同输入可能产生不同输出
* 所以缓存更适合 temperature=0 的确定性场景
*/
@Service
public class ExactCacheService {

private final ChatClient chatClient;
private final StringRedisTemplate redisTemplate;
private static final Duration CACHE_TTL = Duration.ofHours(1);

public ExactCacheService(ChatClient.Builder builder, StringRedisTemplate redisTemplate) {
this.chatClient = builder.build();
this.redisTemplate = redisTemplate;
}

public String cachedChat(String input) {
// 1. 查缓存(O(1),亚毫秒级)
String cacheKey = "ai:cache:" + DigestUtils.md5Hex(input);
String cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null && !cached.isBlank()) {
return cached;
}

// 2. 未命中,调用模型(500ms~10s)
String result = chatClient.prompt().user(input).call().content();

// 3. 写入缓存
redisTemplate.opsForValue().set(cacheKey, result, CACHE_TTL);
return result;
}
}

25.2.2 语义缓存

原理:用户不会每次都用完全相同的措辞。"北京天气怎么样"和"北京今天天气如何"语义相同,应该命中同一缓存。做法是把问题向量化后在向量库中检索,相似度超过阈值就算命中。

/**


🔒 解锁完整内容

本章剩余内容需要解锁后查看

以上仅为本章部分预览内容,完整内容包含更多深度源码解析、实战代码和面试要点。

加入知识星球你将获得:

  • ✅ 全部 26 章完整内容 + 持续更新
  • ✅ 配套源码 + 实战项目
  • ✅ 一对一答疑 + 面试辅导
  • ✅ 简历优化 + 内推机会

📚 本章完整目录

以下为本章完整目录结构,加入知识星球即可解锁全部内容。

25.2.3 两种缓存的对比

25.3 Prompt 精简:少一个 Token 就快一点

25.3.1 原理

25.3.2 Prompt 精简技巧

25.3.3 控制输出长度

25.4 并发控制与限流

25.4.1 为什么需要并发控制

25.4.2 信号量并发控制

25.4.3 令牌桶限流

25.5 流式响应:让用户感觉快

25.5.1 原理

25.5.2 SSE 流式接口

25.6 批量合并:减少调用次数

25.6.1 原理

25.6.2 实现

25.7 异步非阻塞:别让 AI 调用拖垮整个应用

25.7.1 原理

25.7.2 CompletableFuture 异步

25.7.3 异步 RAG 管道

25.8 模型选择:不是所有场景都需要最强模型

25.8.1 原理

25.9 性能优化效果总结