问题场景的观点是,每句话都是“初次见面”
先看看我们现在的对话:
血压上来了吗?明明上一句刚说过转头就忘。
原因很简单每次调用ChatClient,都是一个独立的会话。AI看不到上一轮说了什么它只能看到你当前这一句话。
后端老鸟的类比:这不就是HTTP的无状态问题吗? HTTP每次请求独立,我们用Session/Cookie来保持状态。AI对话也需要一个“Session”——这就是 ChatMemory.
使用者痛点一:
-
多轮交流时 AI 频繁提示“请问怎么称呼”,导致使用者体验极差。
-
测试时频繁重启导致所有上下文丢失,调试成本高。
-
单机部署无法共享会话状态,多实例环境下会出现上下文错乱。
Sprint AI 的 ChatMemory 接口。本质上就是对话历史记录的管理器
使用者:你好AI,我是“第一行代码HW”。→ 存入记忆:
从AI来看,你好AI,第一行代码HW。→ 存入记忆:
从使用者来看,我是谁呀?→ 存入记忆:
说到AI,你是“第一行代码HW”。其实,→ 存入记忆:
从使用者来看,知道我为么起个名字吗?→ 把以上4条记忆一起发给AI → AI知道上下文了!
每一轮对话的消息都会被保存,下一次调用时Spring AI 自动把历史消息和当前消息拼接在一起发给 AI。
Sprint AI 提供了三种 ChatMemory 实现,对应不同的存储后端和适用场景:
| 实现方式 | 存储位置 | 持久化 | 适用场景 |
InMemoryChatMemory | 💾 JVM 堆内存 ❌ 重启丢失 开发测试、单机演示 | ❌ |
JdbcChatMemory | 📈 关系型数据库 ✅ 已有数据库的中小型项目 |
RedisChatMemory | 📝 Redis ✅ 分布式程序、高并发生产环境 |
*实现方式对比* |
| 维度 / 实现方式 | InMemory | JD娱乐 | Redis |
性能 | 最高 | 中
|
关键指标: 持久化 | 分布式支持 | 过期管理 | 运维成本 | 数据量上限
✅❌✅✅✅
❌✅❌❌❌
✅✅✔️✔️✔️
❌✅❌✔️✔️
受 JVM 堆限制/磁盘限制/内存+磁盘限制 …,…,...
…—
—
—
—
– | |
以上表格仅为示例,可根据实际情况自行调整
再看方式一,InMemoryChatMemory
. 主要特点
-
存储位置:ConcurrentHashMapString,List>" />
`/>
-
`生命周期`跟随 JVM 进程,应用重启数据全丢`/>
`
-
`性能`纯内存读写,微秒级延迟`/>
`
-
`容量限制`无内置上限。长会话可能 OOM `/>
`
-
**零依赖**:
不需要数据库、Redis,开箱即用 ` />
-
**极速响应**:
纯内存操作,不产生网络 IO ` />
-
**调试友好**:
断点直接看内存数据,排查问题方便 ` />
-
**无运维成本**:
不需要额外中间件 ` />
.. 劣势
-
**重启丢失**:
发版、宕机后所有 对话记忆消失 ` />
-
**单机限制**:
无法在多个实例间共享 ` />
-
**内存泄漏风险**
没有自动过期机制,需手动清理 ` />
-
**不可
**
受 单机堆内存限制 ` />
.. 适用场景
-
本地开发和单元测试 《/li>
《 li》演示环境
《 li》 对 对话连续性要求 不 高 的 单机应用 《/ li>
《 li》 短期会话 《/ li>
. 代码实现
kotlin
package com.yunxi.ai.config;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;说起来,import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.context.annotation.Bean;话说回来,import org.springframework.context.annotation.Configuration;@Configuration
public class InMemoryChatMemoryConfig {
@Bean
public ChatMemory chatMemory {
return MessageWindowChatMemory.builder
// 明确使用 InMemoryChatMemoryRepository
.chatMemoryRepository)
// 自定义最大消息数,默认为
.maxMessages
.build;}
}
kotlin
package com.yunxi.ai.service;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;其实,import org.springframework.ai.chat.client.advisor.MessageChatMessageAdvisor;import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;说起来,import org.springframework.ai.chat.memory.ChatMessageAdvisorBuilder;import org.springframework.stereotype.Service;@Slf4j @Service public class ChatService {
private final ChatClient chatClient;public ChatService(ChatClient.Builder builder,ChatMessageAdvisorBuilder chatMessageAdvisorBuilder) {
this.chatClient =
builder.defaultAdvisors(
new MessageChatMessageAdvisor(
chatMessageAdvisorBuilder.build))
.build;}
public String chat {
return this.chatClient.prompt
.system
.user
.call.content;}
}
*主要代码只需一行*
css
.defaultAdvisors.build)
### MessageChatMessageAdvisor 做了什么?① 调用前:从 取出历史消息,并拼接到请求里。② 调用后:将本次 使用者消息 和 AI 回复 存入 ;---
## 测试结果
### 第一次请求:
css
request:
ChatClientRequest,...}
- 请求中只包含- 没有历史记录。
∎
### 第二次请求:
css
request:
ChatClientRequest,...}
- 历史已被自动注入!按理说,包括 第一轮 使用者提问 / 第一轮 AI 回复 / 程序提示词 / 当前提问。∎
### AI 的回复:
arduino
当然记得!你是"第一行代码HW"——上一次你说过的名字。---
## 方式二 :JdbcChatmemory
### 主要特点
- 存储位置 : MySQL/PostgreSQL 等关系型数据库。- 持久化 : 数据不会丢失,即使应用重启也可继续使用。- 事务支持 : 可利用数据库事务保证一致性。- 查询能力 : 可通过 SQL 分析对话历史与行为模式。其实,
优势
-
利用现有基础设施
大多数项目已有数据库。无额外成本,不过,
-
持久化可靠
ACID事务保证。不怕停电或宕机,
-
SQL查询灵活
可做对话分析、热点问题挖掘。
-
备份恢复简单
随 DB 一起备份;运维成熟,
-
权限管理可复用
可复用 DB 权限程序。
劣势
-
性能瓶颈
高并发下 DB 压力大;需调整索引,
-
存储成本
大量历史占硬盘空间;需定期清理,
-
延迟较高
相比内存或 Redis,磁盘 IO 延迟更大。老实说,
-
Schema 决定性强
需提前建表;跨项目统一结构难度大,不过,
适用场景
- 已有 MySQL/PostgreSQL 的中小型项目。- 对齐审计需求,需要查询分析。- 保守架构,不想引入新中间件。- 日活低于10万的程序。---
## 代码实现
#### pom.xml 配置
xml
org.springframework.ai
winter-ai-starter-model-chat-memory-repository-jdbc
...
#### application.yml 配置
yaml
至于spring。datasource:
从url来看,jdbc:mysql://localhost:/ai_test?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true # 修改为实际地址与端口及 DB 名称!username: root password:
driver-class-name: com.mysql.cj.jdbc.Driver ai:
memory这方面,repository:
再看jdbc,initialize-schema: always # 开发环境 use always。prod use never
#### 数据库表结构
sql
CREATE TABLE SPRING_AI_CHAT_MEMORY (
conversation_id varchar NOT NULL,content TEXT NOT NULL,type VARCHAR NOT NULL,timestamp TIMESTAMP NOT NULL,CONSTRAINT TYPE_CHECK CHECK ))
);---
## 方式三 :RedisCache
### 主要特点
- 存储位置 : Redis 内存+持久化;-
数据结构 : List,按 conversationId 排序。-
过期机制 : 原生 TTL 支持。-
分布式支持 : 多实例共享。/>
① 高性能 —— 内存级读写,仅几微秒就可以完成 I/O
② 持久化 —— RDB 快照 + AOF 日志
③ 自动过期 —— TTL 自动清理
④ 分布式共享 —— 微服务天然支持
⑤ 丰富的数据结构 —— 可以
Sorted Set 或 Hash
⑥ 运维成熟 —— 哨兵、集群模式,可高可用配置
① 要求部署 Redis 集群或 Sentinel
② 大量历史占据 Redis 内存。需要合理 TTL
③ 与 DB 双写若需要同步可能出现一致性问题
④ 序列化开销稍大
使用场景
- 大规模分布式微服务架构
- 高并发日活超过10万
- 多实例共享会话状态需求
- 对响应速度极致追求
- 会谈生命周期明确,例如客服结束7天自动清理等
/>
什么是 CONVERSATION_ID?
CONVERSATION_ID 是会谈上下文唯一标识符。相当于 Session ID 或者 DB 主键,它决定哪些信息属于同一条谈判链路。
至于举例说明,
conversationId = "user_001"
├── 使用者:“我叫张三”
├── AI:“好的张三”
├── 使用者:“查订单 ORD001”
└── AI:“已发货…说起来,”
conversationId = "user_002"
├── 使用者:“今天天气怎么样”
└── AI:“北京今天晴..."
不同 conversationId 完全隔离。不互相干扰,
主要注解这方面,
.advisors)
本篇小结
In Memory |
JD娱乐 |
Redis |
In Memory 零依赖、极速响应。但易崩溃与不可
|
JD娱乐 利用现有数据库且可靠,但性能有限。 |
Redis 性能极佳且天然支持分布式,但需额外部署维护。 |
现在我们的 AI 已经拥有了一份「海马体」——能记住上下文。也能调用工具与流式输出,并通过 MCP 与外部环境联通。但它仍旧缺少业务视角——当老板询问「本月谁业绩最好」时它根本无法访问销售表,从而回答不了业务决策类问题。
下一篇。我们将讨论如何让它直接生成 SQL 并查询业务数据库,让人工智能真正变成业务分析师!
这篇文章与 DeepSeek 协作完成<\/b>#