Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
告别无状态对话用 Spring AI AlibabaRedis 打造智能体记忆中枢手写一个带记忆的 AI 客服让你的大模型真正“记住”你说过的话现在 AI 应用遍地开花但你会发现大多数 Demo 级别的 AI 对话问完一句就忘了上一句。你跟它说“我叫张三”下一轮问“我叫什么”它一脸懵地回答“你还没有告诉我你的名字”。这就是典型的无状态 AI毫无实用价值。真正能落地的 AI 应用比如智能客服、角色陪伴、多轮对话 Agent核心挑战从来不是“怎么调 API”而是状态管理——如何高效、低成本地管理多轮对话的上下文记忆、控制 Token 消耗、实现历史截断。而这恰恰是 Java 后端工程师的主场。今天我们就用 Spring AI Alibaba Redis手把手打造一个带记忆的 AI 客服让你的 LLM 应用拥有“超强大脑”。一、问题揭示无状态 AI 有多尴尬先来看一个典型“无状态”实现的伪代码PostMapping(「/chat」) public String chat(String userMessage) { // 每次调用都只发当前消息不带历史 return chatClient.call(userMessage); }效果是这样的用户我叫张三。AI好的张三。用户我刚才说我的名字是什么AI抱歉我不知道你的名字你还没有告诉我。更真实一点的场景用户问“我家空调不制冷了怎么办”AI 给出了一系列排查步骤然后用户追问“第二步说的过滤网在哪个位置”AI 完全不知道“第二步”指的是什么。没有记忆的 AI就像一个金鱼——每次对话都是“全新的开始”。在业务场景中这种断片式的交互体验用户根本无法接受。二、核心方案四种记忆存储方案对比在给 AI 加记忆之前先明确我们要存储什么对话历史用户每轮说的话、AI 的回复。会话元数据会话 ID、用户 ID、创建时间、最后活跃时间等。Token 消耗用于计费和截断策略。常见的存储方案及优缺点方案优点缺点适用场景客户端存储LocalStorage零服务端成本不安全、不可跨端、数据易丢失纯前端演示内存缓存ConcurrentHashMap实现简单、极快无法集群共享、重启丢失、内存不可控单机原型 Redis方案优点缺点适用场景客户端存储LocalStorage零服务端成本不安全、不可跨端、数据易丢失纯前端演示内存缓存ConcurrentHashMap实现简单、极快无法集群共享、重启丢失、内存不可控单机原型Redis高性能、支持集群、过期策略、数据结构丰富需要额外组件生产级首选数据库MySQL持久化、可复杂查询性能较低、不适合高频读写需要长期存档审计高性能、支持集群、过期策略、数据结构丰富需要额外组件生产级首选数据库MySQL持久化、可复杂查询性能较低、不适合高频读写需要长期存档审计对于大多数对话场景Redis 是最佳平衡点。尤其是在生产环境中我们需要多节点集群共享记忆——用户请求可能被负载均衡到任意一台服务器如果某台机器把对话记忆存在本地内存里换一台机器就全丢了。而 Redis 作为集中式存储天然支持跨节点共享。本文基于 Spring AI Alibaba 框架 Redis实现一个支持滑动窗口、自动截断的生产级记忆方案。为什么选择 Spring AI AlibabaSpring AI Alibaba 是阿里云推出的面向 Java 开发者的 AI 接入方案构建在 Spring AI 基础之上对接了阿里云的 DashScope 平台 通义千问系列模型。它与 Spring Boot 紧密集成符合传统 Java 编程习惯封装了复杂的底层通信逻辑支持 Prompt 模板、多轮对话、RAG 检索增强等能力。在国内 Java 生态中使用广泛特别是需要对接通义大模型或 阿里百炼平台的项目。三、实战代码Spring AI Alibaba Redis 实现滑动窗口记忆3.1 技术栈Spring Boot 3.xSpring AI Alibaba 1.1.2.0Redis推荐使用 Redis 6.x阿里云 DashScope通义千问模型3.2 项目搭建与依赖配置首先在pom.xml中添加 Spring AI Alibaba 的依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.10/version /parent properties Java.version17/Java.version /properties dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI Alibaba DashScope Starter -- dependency groupIdcom.alibaba.spring/groupId artifactIdspring-ai-alibaba-starter/artifactId version1.1.2.0/version /dependency !-- Redis 整合 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-Redis/artifactId /dependency !-- Jackson JSON 序列化 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency !-- Commons Pool 连接池 -- dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency /dependencies接着配置 API Key 和 Redis 连接信息# application.yml spring: data: Redis: host: localhost port: 6379 timeout: 5000ms lettuce: pool: max-active: 8 max-idle: 8 ai: dashscope: api-key: sk-xxx-your-real-key注意API Key 可以在 阿里云 DashScope 控制台获取。3.3 核心数据结构设计我们用 Redis 的 List 结构存储某个会话的对话历史每条记录包含角色、内容和时间戳// 对话消息实体 Data NoArgsConstructor AllArgsConstructor public class ChatMessage { private String role; // 「user」 或 「assistant」 private String content; // 消息内容 private Long timestamp; // 时间戳用于排序和过期判断 }Redis Key 命名规则chat:history:{sessionId}3.4 实现基于 Redis 的 ChatMemoryRepositorySpring AI Alibaba 提供了ChatMemoryRepository接口作为存储抽象层我们需要实现一个 Redis 版本负责消息的持久化存储和检索Component public class RedisChatMemoryRepository implements ChatMemoryRepository { Autowired private RedisTemplateString, Object redisTemplate; private static final String KEY_PREFIX 「chat:history:」; private static final int MAX_HISTORY_SIZE 20; // 最大历史消息数超过则滑动窗口截断 Override public ListMessage findByConversationId(String conversationId) { String key KEY_PREFIX conversationId; ListObject range redisTemplate.opsForList().range(key, 0, -1); if (range null || range.isEmpty()) { return new ArrayList(); } // 将存储的 ChatMessage 转换为 Spring AI 的 Message 对象 return range.stream() .map(obj - { ChatMessage msg (ChatMessage) obj; return new Message(msg.getRole(), msg.getContent()); }) .collect(Collectors.toList()); } Override public void saveAll(String conversationId, ListMessage messages) { String key KEY_PREFIX conversationId; // 将 Message 转换为 ChatMessage 并存储 for (Message msg : messages) { ChatMessage chatMsg new ChatMessage( msg.getRole(), msg.getContent(), System.currentTimeMillis() ); redisTemplate.opsForList().rightPush(key, chatMsg); } // 限制队列长度实现滑动窗口 Long size redisTemplate.opsForList().size(key); if (size ! null size MAX_HISTORY_SIZE) { // 弹出超出部分的老消息 for (int i 0; i size - MAX_HISTORY_SIZE; i) { redisTemplate.opsForList().leftPop(key); } } // 设置会话过期时间30 分钟无活动则自动清理 redisTemplate.expire(key, 30, TimeUnit.MINUTES); } Override public void deleteByConversationId(String conversationId) { String key KEY_PREFIX conversationId; redisTemplate.delete(key); } Override public ListString findConversationIds() { // 简化实现生产环境可通过 SCAN 命令遍历 throw new UnsupportedOperationException(「如需遍历会话 ID请使用 KeysCommand 或 SCAN 命令」); } }3.5 配置 ChatMemory通过配置类将我们的 Redis 存储库绑定到滑动窗口记忆策略上Configuration public class ChatMemoryConfig { Bean public ChatMemoryRepository chatMemoryRepository() { // 使用我们实现的 Redis 版本 return new RedisChatMemoryRepository(); } Bean public ChatMemory chatMemory(ChatMemoryRepository repository) { // MessageWindowChatMemory 是 Spring AI 提供的内置实现维护一个固定大小的消息窗口 // 当消息数量超过 maxMessages 时会自动移除最老的消息同时保留系统消息[reference:6][reference:7] return MessageWindowChatMemory.builder() .chatMemoryRepository(repository) .maxMessages(20) // 最大保留 20 条消息约 10 轮对话 .build(); } }3.6 将 ChatMemory 集成到 ChatClientSpring AI Alibaba 通过Advisor顾问机制来为 ChatClient 添加增强功能。MessageChatMemoryAdvisor会在每次请求时自动从记忆库加载历史消息并附加到当前 Prompt 中Configuration public class ChatClientConfig { Bean public ChatClient chatClient(ChatMemory chatMemory) { return ChatClient.builder() // 添加记忆增强顾问自动注入历史对话 .build(advisor - advisor .with(MessageChatMemoryAdvisor.class, advisorSpec - advisorSpec .chatMemory(chatMemory)) ); } }3.7 完整的 Controller带记忆的聊天接口RestController RequestMapping(「/api/chat」) public class ChatController { Autowired private ChatClient chatClient; PostMapping(「/send」) public ResponseEntityChatResponse sendMessage(RequestBody ChatRequest request) { String sessionId request.getSessionId(); // 由前端传入如 UUID String userMessage request.getMessage(); // chatClient 会自动根据 conversationId 参数加载历史记忆 // Spring AI 的 MessageChatMemoryAdvisor 会识别 conversationId 参数 // 自动从对应的记忆存储中加载历史消息并注入到本次请求中[reference:9] String aiReply chatClient.prompt() .user(userMessage) .withSystemParam(「conversationId」, sessionId) // 关键绑定会话 ID实现会话隔离 .call() .content(); return ResponseEntity.ok(new ChatResponse(aiReply, sessionId)); } }注意Spring AI 的 MessageChatMemoryAdvisor 基于 AOP 实现环绕增强逻辑。当我们在请求中传入conversationId参数后Advisor 会自动完成两件事请求前从记忆库加载历史消息合并到 Prompt 中请求后将本轮问答结果自动保存回记忆库。开发者无需手动维护任何存储逻辑。3.8 效果演示启动项目后用 curl 测试curl -X POST HTTP://localhost:8080/api/chat/send \ -H 「Content-Type: application/json」 \ -d 『{「sessionId」: 「user-001」, 「message」: 「我叫张三」}』 # 返回: 「你好张三很高兴认识你。有什么我可以帮你的吗」 curl -X POST HTTP://localhost:8080/api/chat/send \ -H 「Content-Type: application/json」 \ -d 『{「sessionId」: 「user-001」, 「message」: 「我刚才说我叫什么」}』 # 返回: 「你刚才说你叫张三。」同一个sessionId下AI 记住了之前的对话。换一个sessionId对话历史完全独立互不干扰——这就是会话隔离。四、进阶优化Token 消耗控制与智能截断真实场景下对话越长发送给 LLM 的 token 就越多费用和延迟都会增加。我们需要更精细的控制。4.1 滑动窗口内存修剪我们已经在RedisChatMemoryRepository.saveAll()中实现了滑动窗口机制通过限制MAX_HISTORY_SIZE来控制记忆长度。需要调整窗口大小时直接修改配置即可。对于更精细的控制Spring AI 的MessageWindowChatMemory支持自定义消息窗口大小。4.2 按 Token 数智能截断如果单纯按消息数量截断不够精细有的消息短有的消息长可以考虑实现按 Token 数量截断。借助阿里百炼平台提供的tiktoken-Java库public ListMessage getRecentWithinTokenLimit(String conversationId, int maxTokens) { ListMessage all chatMemory.get(conversationId); ListMessage result new ArrayList(); int currentTokens 0; for (int i all.size() - 1; i 0; i--) { Message msg all.get(i); int tokens encoding.encode(msg.getContent()).size(); if (currentTokens tokens maxTokens) break; currentTokens tokens; result.add(0, msg); } return result; }4.3 长对话摘要压缩当对话轮次超过阈值后可以触发后台任务使用 LLM 本身将旧对话总结为摘要替换原始历史。Spring AI Alibaba 支持通过ReactAgent的上下文工程Context Engineering技术实现这一能力通过消息修剪Message Trimming和摘要生成Summarization来管理长对话中的上下文过载问题。五、进阶思路向量数据库与阿里百炼长期记忆以上方案使用 Redis 存储短期记忆最近 N 轮对话适合会话级多轮交互。但如果要实现“永久记忆”——比如 AI 能记住用户一年前说过的喜好、历史订单、过往投诉——就需要长期记忆方案。5.1 向量数据库方案通过向量数据库如 Milvus、Qdrant、PgVector配合嵌入模型可以为记忆建立语义索引Embedding把每轮对话内容用嵌入模型转为向量。存储把向量连同元数据存入向量数据库。检索新消息到来时同样转为向量查询最相似的 K 条历史记忆注入到 Prompt 中。Spring AI Alibaba 已提供 VectorStore、Retriever、DocumentReader 等模块化组件支持 Elasticsearch、Redis、PGVector 等后端。5.2 阿里百炼“记忆库”功能更便捷的选择是利用阿里百炼平台提供的记忆库Memory Vault功能。该功能于 2026 年 4 月正式上线让 Agent 具备跨会话的长期记忆能力真正实现“越聊越懂用户”的个性化体验。记忆库系统内置了“提取-存储-检索-注入”四大模块用户每次与 AI Agent 对话结束后系统可根据配置的记忆规则自动提取关键信息并存储当用户再次提问时系统会触发语义检索召回相关记忆并附加至上下文中实现个性化回答。开发者可通过 API 直接调用同时 Spring AI Alibaba 已深度集成阿里百炼平台支持 DashScope Agent 与记忆系统的无缝对接。六、生产环境最佳实践与踩坑指南6.1 不同用户/会话的隔离在调用时务必通过withSystemParam(「conversationId」, sessionId)为不同用户传入不同的会话标识。同一会话 ID 自动共享记忆不同会话 ID 之间完全隔离避免“串话”问题。6.2 Redis 连接池配置生产环境中Redis 的高并发至关重要建议spring: data: Redis: lettuce: pool: max-active: 20 max-idle: 10 min-idle: 5 max-wait: 10000ms6.3 记忆过期策略管理对于不再活跃的会话应自动清理避免 Redis 内存膨胀。可以在RedisChatMemoryRepository.saveAll()中添加过期时间// 设置会话 30 分钟无活动则自动过期 redisTemplate.expire(key, 30, TimeUnit.MINUTES);根据业务场景灵活调整 TTL 时长高频对话场景可设为 1 小时离线场景可设为 7 天。6.4 工具调用结果的记忆回写如果你的 Agent 调用了外部工具如查询订单、获取天气记得将工具返回结果以系统消息形式回写到记忆。否则后续轮次中 Agent 不知道它自己之前查到了什么信息// 工具执行后将结果追加到记忆 String toolResult orderService.query(userId, lastWeek); memory.add(「system」, 「工具返回」 toolResult);七、总结你的 AI 应用离“智能”只差一个记忆中枢今天我们从一个痛点出发逐步实现了一个生产级的 AI 对话记忆方案问题无状态 LLM 毫无价值无法支撑真实业务场景。方案对比Redis 是生产最优解支持分布式部署和会话隔离。实战用 Spring AI Alibaba Redis 实现滑动窗口记忆代码可直接复用。框架优势Spring AI Alibaba 提供ChatMemoryRepository存储抽象、MessageWindowChatMemory滑动窗口实现、以及MessageChatMemoryAdvisor自动增强机制——开发者只需专注业务框架负责复杂的记忆管理逻辑。优化Token 截断、摘要压缩。进阶向量数据库实现长期记忆、阿里百炼“记忆库”功能限时免费可直接通过 API 调用。下一步你可以做什么按照本文代码10 分钟内跑通一个带记忆的 AI 客服 Demo。根据业务需求调整记忆窗口大小和会话过期时间。在阿里百炼控制台探索“记忆库”功能为你的应用加上长期记忆。阅读 Spring AI Alibaba 官方文档中关于ChatMemory和 Agent 框架的更多内容。