Spring Boot + LangChain4j Day 5:Redis 会话记忆持久化

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、踩坑