Day 5 --- Redis 会话记忆持久化
🎯 目标 :把对话历史从 JVM 内存迁到 Redis,服务重启后继续聊。
⏮️ Day 4 回顾 :PGVector 解决了知识库持久化,但对话历史还是重启就丢
⏭️ 本日成就:知识库 + 对话历史双持久化,服务可以放心重启
一、为什么需要 Redis 会话记忆?
之前的痛点
Day 2: HashMap<Integer, Object> userSessions
├── 重启 → 全丢
├── 无过期机制 → 内存越用越多
└── 多实例部署 → 无法共享
Day 5 的方案
Day 5: RedisChatMemoryStore
├── 重启 → 从 Redis 读回,继续聊 ✅
├── TTL 30 分钟 → 无活动自动过期 ✅
├── 多实例 → 共享同一个 Redis ✅
└── FIFO 滑动窗口 → 最多 20 条消息 ✅
二、核心变化
| 维度 | Day 2/3/4 | Day 5 |
|---|---|---|
| 记忆存储 | HashMap<Integer, Object> (JVM) |
Redis (chat:memory:{sessionId}) |
| 重启 | 全丢 | 不丢 |
| 过期 | 无 | TTL 30 分钟 |
| 序列化 | 无(JVM 对象引用) | Jackson JSON |
| 多实例 | 不支持 | 天然支持 |
三、项目结构
day5/
├── pom.xml # 新增: spring-boot-starter-data-redis
├── src/main/java/com/day5/demo/
│ ├── Day5Application.java # 入口
│ ├── config/
│ │ ├── Assistant.java # Agent 接口(@SystemMessage 提示词)
│ │ ├── ChatModelConfig.java # 模型 Bean(Chat + Embedding + PGVector)
│ │ ├── LangChain4jConfig.java # Agent 装配(ChatMemoryProvider → Redis)
│ │ └── RedisConfig.java # ★ Redis 序列化配置(Jackson2Json)
│ ├── memory/
│ │ └── RedisChatMemoryStore.java # ★ 实现了 ChatMemoryStore 接口
│ ├── controller/
│ │ ├── AgentController.java # GET /agent/chat
│ │ └── DocumentController.java # POST /documents/upload
│ ├── service/
│ │ ├── RagService.java # 启动时预加载 classpath 文档
│ │ └── DocumentService.java # PDF/TXT 解析 + 分批向量化
│ └── tool/
│ ├── WeatherTool.java # 天气查询 + 数学运算
│ └── SearchTool.java # 当前时间 + 订单查询
└── src/main/resources/
├── application.yml # Redis 连接 + 记忆配置
└── docs/码哥科技.txt # 示例知识库
四、核心代码解读
4.1 RedisChatMemoryStore
java
public class RedisChatMemoryStore implements ChatMemoryStore {
// Redis Key: chat:memory:{sessionId}
// Value: JSON 序列化的 List<ChatMessage>
// TTL: chat.memory.ttl-minutes 分钟后自动删除
@Override
public List<ChatMessage> getMessages(Object sessionId) {
// 1. 从 Redis 读取
// 2. 刷新 TTL(只有活动会话才保留)
// 3. 返回消息列表
}
@Override
public void updateMessages(Object sessionId, List<ChatMessage> messages) {
// 1. 超过 maxMessages → 丢弃最早的消息(滑动窗口)
// 2. 写入 Redis + 设置 TTL
}
}
4.2 RedisConfig --- 序列化关键
java
// ⚠️ 默认 JDK 序列化要求对象实现 Serializable
// ChatMessage 不一定实现 → 存/取报 NotSerializableException
// 解决方案:用 Jackson2JsonRedisSerializer,存 JSON
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
4.3 LangChain4jConfig --- 记忆接入 Agent
java
@Bean
public ChatMemoryProvider chatMemoryProvider(RedisChatMemoryStore store) {
return sessionId -> MessageWindowChatMemory.builder()
.id(sessionId)
.maxMessages(maxMessages)
.chatMemoryStore(store) // ← 这里!HashMap → Redis
.build();
}
五、API 一览
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /agent/chat?message=你好 |
对话(自动从 Redis 加载/存储记忆) |
| POST | /documents/upload |
上传文档(TXT/PDF/MD) |
六、运行
前置条件
bash
# 确认 Docker 容器在运行
docker ps | findstr "ai-postgres"
docker ps | findstr "ai-redis"
启动
powershell
$env:SILICONFLOW_API_KEY = "sk-your-key"
cd day5
mvn spring-boot:run
七、核心测试:重启验证记忆
bash
# Step 1: 对话
curl "http://localhost:8080/agent/chat?message=我叫张三,我喜欢Java"
# Step 2: 验证记忆
curl "http://localhost:8080/agent/chat?message=我叫什么名字"
# Step 3: 停掉服务 (Ctrl+C),重新启动
mvn spring-boot:run
# Step 4: 重启后再问
curl "http://localhost:8080/agent/chat?message=我叫什么名字"
# ← 还能回答"张三" → 记忆持久化生效 ✅
验证 Redis 里的数据
bash
# 查看所有会话 key
docker exec -it ai-redis redis-cli KEYS "chat:memory:*"
# 查看某个会话的内容
docker exec -it ai-redis redis-cli GET "chat:memory:user1"
八、技术栈
| 组件 | 版本/型号 | 作用 |
|---|---|---|
| Spring Boot | 4.1.0 | 应用框架 |
| Java | 17 | 运行环境 |
| LangChain4j | 0.36.2 | AI 编排 |
| DeepSeek-V3 | 硅基流动 | 对话模型 |
| bge-large-zh-v1.5 | 硅基流动 | 嵌入模型 |
| PostgreSQL + PGVector | 17 | 向量库(知识库持久化) |
| Redis | 7 | 会话记忆持久化 ← Day 5 新增 |
| PDFBox | 3.0.3 | PDF 解析 |
| Jackson | Spring Boot 内置 | JSON 序列化 |
九、Redis 数据结构速查
Redis Key: chat:memory:user1
Value (JSON): [{"type":"SYSTEM","text":"..."},
{"type":"USER","text":"我叫张三"},
{"type":"AI","text":"你好张三!"},
...]
TTL: 30 分钟(每次读取自动刷新)
过期策略: 无活动 30 分钟自动删除
窗口大小: 20 条消息(超出自动丢弃最早)
10、踩坑
