第07课:Agent会话记忆持久化设计——如何将上下文记忆存入数据库,实现会话中断后恢复、跨设备同步
从零搭建工业级Java Agent:Claude Code架构拆解12节完整教程
引言:随着AI Agent技术的爆发,越来越多开发者想从0到1掌握Agent的设计与落地,但市面上大多教程停留在“概念讲解”,缺乏与真实项目结合的实操指导。本文基于Claude Code(Java版)项目,拆解12节渐进式课程,从Agent基础概念到工业级工程化落地,每一节都对应真实项目代码,看完就能上手搭建自己的AI Agent,适合Java开发者、AI工程爱好者学习。
第07课:Agent会话记忆持久化设计——如何将上下文记忆存入数据库,实现会话中断后恢复、跨设备同步
一、前言
上一节课我们完成了Agent的“安全防线”——权限控制系统,实现了细粒度权限管控,防止工具滥用和数据泄露。但在实际使用场景中,Agent的会话记忆(上下文信息)若仅存储在内存中,会面临两个核心问题:一是会话中断(如Agent重启、服务宕机)后,上下文记忆全部丢失,Agent无法继续之前的任务;二是跨设备使用时,会话记忆无法同步,用户体验极差。
例如:用户在电脑端让Agent执行一个复杂的编码任务,执行到一半关闭Agent,再次启动后,Agent完全忘记之前的任务要求和执行进度,需要用户重新描述需求;或者用户在手机端启动Agent,无法获取电脑端之前的会话记录,无法无缝衔接任务。
因此,会话记忆持久化是工业级Agent的必备功能,核心是将Agent的上下文记忆(用户输入、工具调用记录、Agent推理过程)持久化到数据库中,实现会话中断后恢复、跨设备同步,同时保证记忆读取和存储的高效性。本节课我们拆解Claude Code中MemoryStorage(记忆存储)和SessionManager(会话管理)的源码实现,教你搭建一个高可靠、可扩展的Agent会话记忆持久化系统。
核心结论:Agent会话记忆持久化的核心是“分层存储、高效读写、会话隔离”——将短期记忆和长期记忆分层存储,优化读写性能,通过会话ID实现不同用户、不同会话的记忆隔离,确保数据安全和一致性。
二、会话记忆持久化的核心需求与设计原则
1. 核心需求(工业级场景必满足)
工业级Agent的会话记忆持久化系统,需解决以下5个核心问题,兼顾实用性和安全性:
(1)会话记忆持久化:将用户输入、工具调用记录、Agent推理结果等上下文信息,持久化到数据库,避免内存丢失;
(2)会话中断恢复:Agent重启、服务宕机后,可通过会话ID恢复之前的会话记忆,继续执行未完成的任务;
(3)跨设备同步:同一用户在不同设备登录后,可获取所有历史会话记录,实现任务无缝衔接;
(4)高效读写:记忆的存储和读取速度要快,避免影响Agent的响应效率(尤其是高频交互场景);
(5)会话隔离与安全:不同用户、不同会话的记忆相互隔离,防止数据泄露;同时支持会话删除、过期清理,释放存储资源。
2. 核心设计原则(兼顾性能与安全)
- 分层存储原则:将短期记忆(当前会话的临时上下文,如最近5轮交互)和长期记忆(历史会话记录、重要任务信息)分层存储,短期记忆可缓存到本地,长期记忆存入数据库,平衡读写性能;
- 会话唯一标识:每个会话分配唯一的会话ID(SessionId),关联用户ID,实现“用户-会话-记忆”的三层关联,确保会话隔离;
- 高效索引:在数据库中建立会话ID、用户ID索引,提升记忆查询速度,避免大量数据查询卡顿;
- 可扩展性:支持多种数据库(如MySQL、Redis),便于根据实际场景切换存储方案;同时支持记忆压缩,减少存储占用;
- 数据一致性:采用事务机制,确保记忆的存储、更新、删除操作原子性,避免数据错乱(如会话记忆部分存储成功、部分丢失)。
三、会话记忆持久化核心架构(Claude Code实战)
Claude Code的会话记忆持久化系统采用“缓存+数据库”的双层架构,核心分为4层,自上而下依次为:会话管理层、记忆分层层、存储适配层、数据存储层,每层职责清晰,协同实现记忆持久化、会话恢复和跨设备同步,架构如下:
1. 架构分层详解
(1)会话管理层(顶层):负责会话的创建、查询、删除、过期管理,核心类为SessionManager,生成唯一会话ID,关联用户ID,管理会话生命周期;
(2)记忆分层层(中间层):将会话记忆分为短期记忆和长期记忆,短期记忆(临时上下文)存储在本地缓存(如Caffeine),长期记忆(历史会话)存储到数据库,实现分层管理;
(3)存储适配层(中间层):提供统一的存储接口,适配不同的存储方案(MySQL、Redis),核心类为MemoryStorage,屏蔽底层存储差异,便于扩展;
(4)数据存储层(底层):实际存储会话记忆数据,Claude Code默认采用“MySQL+Redis”组合:Redis存储短期记忆(缓存),MySQL存储长期记忆(持久化),兼顾性能和可靠性。
补充说明:双层存储的优势是“高性能+高可靠”——短期记忆从Redis读取,速度快,满足高频交互需求;长期记忆存入MySQL,确保数据不丢失,即使Redis宕机,也可从MySQL恢复会话记忆。
四、核心组件源码解析(Claude Code重点类)
Claude Code的会话记忆持久化系统核心包为com.claudecode.memory,包含3个核心类:SessionManager(会话管理)、MemoryStorage(记忆存储)、MemoryEntity(记忆实体),同时依赖UserManager(用户管理)实现会话与用户的关联,依赖数据库连接池实现数据读写,我们逐一拆解其核心实现。
1. 记忆实体类:MemoryEntity(记忆数据封装)
用于封装会话记忆的核心数据,对应数据库中的记忆表,存储用户输入、Agent响应、工具调用记录、会话ID、用户ID等信息,是记忆持久化的基础。
package com.claudecode.memory;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 会话记忆实体类,对应数据库memory表,封装记忆核心数据
*/
@Data
public class MemoryEntity {
// 主键ID(自增)
private Long id;
// 会话ID(唯一标识,关联会话表)
private String sessionId;
// 用户ID(关联用户表,实现会话隔离)
private String userId;
// 记忆类型:SHORT(短期记忆)、LONG(长期记忆)
private MemoryType memoryType;
// 记忆内容(JSON格式,存储用户输入、Agent响应、工具调用记录等)
private String content;
// 记忆创建时间
private LocalDateTime createTime;
// 记忆更新时间(短期记忆会频繁更新)
private LocalDateTime updateTime;
// 过期时间(短期记忆可设置过期时间,自动清理)
private LocalDateTime expireTime;
/**
* 记忆类型枚举
*/
public enum MemoryType {
SHORT("短期记忆", 1), // 临时上下文,如最近5轮交互,存Redis
LONG("长期记忆", 2); // 历史会话记录,存MySQL
private final String typeName;
private final int typeCode;
MemoryType(String typeName, int typeCode) {
this.typeName = typeName;
this.typeCode = typeCode;
}
public String getTypeName() {
return typeName;
}
public int getTypeCode() {
return typeCode;
}
}
// 构造方法(用于快速创建记忆实体)
public MemoryEntity(String sessionId, String userId, MemoryType memoryType, String content) {
this.sessionId = sessionId;
this.userId = userId;
this.memoryType = memoryType;
this.content = content;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
// 短期记忆默认过期时间:1小时(可配置)
this.expireTime = memoryType == MemoryType.SHORT ? LocalDateTime.now().plusHours(1) : null;
}
}
2. 记忆存储接口:MemoryStorage(统一存储适配)
定义统一的记忆存储接口,提供记忆的新增、查询、更新、删除方法,适配不同的存储方案(MySQL、Redis),实现“一次编码,多存储适配”,便于后续扩展。Claude Code提供了两个实现类:RedisMemoryStorage(短期记忆存储)、MysqlMemoryStorage(长期记忆存储)。
package com.claudecode.memory;
import java.util.List;
/**
* 记忆存储统一接口,定义记忆的CRUD操作,适配不同存储方案
*/
public interface MemoryStorage {
/**
* 新增记忆
* @param memoryEntity 记忆实体
* @return 新增成功返回true,失败返回false
*/
boolean addMemory(MemoryEntity memoryEntity);
/**
* 根据会话ID查询记忆(支持分页,默认查询最近10条)
* @param sessionId 会话ID
* @param pageNum 页码
* @param pageSize 每页条数
* @return 记忆列表
*/
List<MemoryEntity> getMemoryBySessionId(String sessionId, int pageNum, int pageSize);
/**
* 根据会话ID和记忆类型查询记忆
* @param sessionId 会话ID
* @param memoryType 记忆类型(SHORT/LONG)
* @return 记忆列表
*/
List<MemoryEntity> getMemoryBySessionIdAndType(String sessionId, MemoryEntity.MemoryType memoryType);
/**
* 更新记忆(主要用于更新短期记忆的内容)
* @param memoryEntity 记忆实体(需包含id或sessionId+memoryType)
* @return 更新成功返回true,失败返回false
*/
boolean updateMemory(MemoryEntity memoryEntity);
/**
* 根据会话ID删除记忆(删除该会话的所有记忆)
* @param sessionId 会话ID
* @return 删除成功返回true,失败返回false
*/
boolean deleteMemoryBySessionId(String sessionId);
/**
* 清理过期记忆(主要清理短期记忆)
* @return 清理的记忆条数
*/
int cleanExpiredMemory();
}
3. 存储实现类:RedisMemoryStorage(短期记忆存储)
基于Redis实现短期记忆的存储,利用Redis的过期键功能,自动清理过期的短期记忆,提升读写性能,适合存储临时上下文信息。
package com.claudecode.memory.impl;
import com.claudecode.memory.MemoryEntity;
import com.claudecode.memory.MemoryStorage;
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 基于Redis的短期记忆存储实现,用于存储临时上下文
*/
@Component
public class RedisMemoryStorage implements MemoryStorage {
// Redis键前缀(区分不同类型的记忆)
private static final String REDIS_KEY_PREFIX = "agent:memory:short:";
// Redis模板(操作Redis)
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean addMemory(MemoryEntity memoryEntity) {
// 仅处理短期记忆
if (memoryEntity.getMemoryType() != MemoryEntity.MemoryType.SHORT) {
throw new RuntimeException("Redis仅支持短期记忆存储");
}
// 构建Redis键:前缀+会话ID
String redisKey = REDIS_KEY_PREFIX + memoryEntity.getSessionId();
// 记忆内容转为JSON字符串
String memoryJson = JSON.toJSONString(memoryEntity);
// 计算过期时间(从当前时间到记忆过期时间的间隔)
Duration expireDuration = Duration.between(LocalDateTime.now(), memoryEntity.getExpireTime());
// 存入Redis,设置过期时间
stringRedisTemplate.opsForValue().set(redisKey, memoryJson, expireDuration);
return true;
}
@Override
public List<MemoryEntity> getMemoryBySessionId(String sessionId, int pageNum, int pageSize) {
String redisKey = REDIS_KEY_PREFIX + sessionId;
String memoryJson = stringRedisTemplate.opsForValue().get(redisKey);
List<MemoryEntity> memoryList = new ArrayList<>();
if (memoryJson != null) {
// Redis中存储的是单条短期记忆(当前会话的临时上下文),转为实体类加入列表
MemoryEntity memoryEntity = JSON.parseObject(memoryJson, MemoryEntity.class);
memoryList.add(memoryEntity);
}
// 短期记忆无需分页(仅存储当前会话的临时上下文),直接返回列表
return memoryList;
}
@Override
public List<MemoryEntity> getMemoryBySessionIdAndType(String sessionId, MemoryEntity.MemoryType memoryType) {
// 仅处理短期记忆
if (memoryType != MemoryEntity.MemoryType.SHORT) {
return new ArrayList<>();
}
return getMemoryBySessionId(sessionId, 1, 10);
}
@Override
public boolean updateMemory(MemoryEntity memoryEntity) {
// 仅处理短期记忆,更新逻辑与新增一致(覆盖原有值)
return addMemory(memoryEntity);
}
@Override
public boolean deleteMemoryBySessionId(String sessionId) {
String redisKey = REDIS_KEY_PREFIX + sessionId;
// 删除Redis中的键
return Boolean.TRUE.equals(stringRedisTemplate.delete(redisKey));
}
@Override
public int cleanExpiredMemory() {
// Redis会自动清理过期键,无需手动清理,返回0即可
return 0;
}
}
4. 存储实现类:MysqlMemoryStorage(长期记忆存储)
基于MySQL实现长期记忆的存储,确保历史会话记录不丢失,支持分页查询、会话删除等功能,适合存储需要长期保留的会话记忆。
package com.claudecode.memory.impl;
import com.claudecode.memory.MemoryEntity;
import com.claudecode.memory.MemoryStorage;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 基于MySQL的长期记忆存储实现,用于存储历史会话记录
*/
@Component
public class MysqlMemoryStorage implements MemoryStorage {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
@Transactional
public boolean addMemory(MemoryEntity memoryEntity) {
// 仅处理长期记忆
if (memoryEntity.getMemoryType() != MemoryEntity.MemoryType.LONG) {
throw new RuntimeException("MySQL仅支持长期记忆存储");
}
// 插入SQL(memory表需提前创建,字段与MemoryEntity对应)
String sql = "INSERT INTO memory (session_id, user_id, memory_type, content, create_time, update_time, expire_time) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
// 执行插入
int rows = jdbcTemplate.update(sql,
memoryEntity.getSessionId(),
memoryEntity.getUserId(),
memoryEntity.getMemoryType().getTypeCode(),
memoryEntity.getContent(),
memoryEntity.getCreateTime(),
memoryEntity.getUpdateTime(),
memoryEntity.getExpireTime()
);
// 插入成功返回true(影响行数>=1)
return rows > 0;
}
@Override
public List<MemoryEntity> getMemoryBySessionId(String sessionId, int pageNum, int pageSize) {
// 计算分页偏移量(pageNum从1开始)
int offset = (pageNum - 1) * pageSize;
// 查询SQL(按创建时间倒序,最新的记忆排在前面)
String sql = "SELECT id, session_id AS sessionId, user_id AS userId, " +
"memory_type AS memoryType, content, create_time AS createTime, " +
"update_time AS updateTime, expire_time AS expireTime " +
"FROM memory WHERE session_id = ? ORDER BY create_time DESC LIMIT ?, ?";
// 执行查询,将结果映射为MemoryEntity列表
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(MemoryEntity.class),
sessionId, offset, pageSize);
}
@Override
public List<MemoryEntity> getMemoryBySessionIdAndType(String sessionId, MemoryEntity.MemoryType memoryType) {
// 仅处理长期记忆
if (memoryType != MemoryEntity.MemoryType.LONG) {
return List.of();
}
// 查询SQL(按创建时间倒序)
String sql = "SELECT id, session_id AS sessionId, user_id AS userId, " +
"memory_type AS memoryType, content, create_time AS createTime, " +
"update_time AS updateTime, expire_time AS expireTime " +
"FROM memory WHERE session_id = ? AND memory_type = ? ORDER BY create_time DESC";
// 执行查询
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(MemoryEntity.class),
sessionId, memoryType.getTypeCode());
}
@Override
@Transactional
public boolean updateMemory(MemoryEntity memoryEntity) {
// 仅处理长期记忆
if (memoryEntity.getMemoryType() != MemoryEntity.MemoryType.LONG) {
throw new RuntimeException("MySQL仅支持长期记忆更新");
}
// 更新SQL(根据id更新内容和更新时间)
String sql = "UPDATE memory SET content = ?, update_time = ? WHERE id = ?";
int rows = jdbcTemplate.update(sql,
memoryEntity.getContent(),
LocalDateTime.now(),
memoryEntity.getId()
);
// 更新成功返回true(影响行数>=1)
return rows > 0;
}
@Override
@Transactional
public boolean deleteMemoryBySessionId(String sessionId) {
// 删除SQL(删除该会话的所有长期记忆)
String sql = "DELETE FROM memory WHERE session_id = ?";
int rows = jdbcTemplate.update(sql, sessionId);
// 删除成功返回true(影响行数>=1)
return rows > 0;
}
@Override
@Transactional
public int cleanExpiredMemory() {
// 长期记忆默认不过期,此处可根据实际需求添加清理逻辑(如清理3个月前的历史记忆)
String sql = "DELETE FROM memory WHERE create_time < ?";
// 清理3个月前的记忆
LocalDateTime threeMonthsAgo = LocalDateTime.now().minusMonths(3);
int rows = jdbcTemplate.update(sql, threeMonthsAgo);
return rows;
}
}
5. 会话管理器:SessionManager(会话全生命周期管理)
负责会话的创建、查询、删除、过期管理,生成唯一会话ID,关联用户ID,协调短期记忆和长期记忆的存储,是会话记忆持久化系统的“调度中心”。
package com.claudecode.memory;
import com.claudecode.core.UserManager;
import com.claudecode.memory.impl.MysqlMemoryStorage;
import com.claudecode.memory.impl.RedisMemoryStorage;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
/**
* 会话管理器,负责会话的创建、查询、删除、过期管理,协调记忆存储
*/
@Component
public class SessionManager {
// 单例模式(结合Spring依赖注入,无需手动实现单例)
@Resource
private RedisMemoryStorage redisMemoryStorage; // 短期记忆存储
@Resource
private MysqlMemoryStorage mysqlMemoryStorage; // 长期记忆存储
@Resource
private UserManager userManager; // 用户管理器(校验用户合法性)
/**
* 创建会话(生成唯一会话ID,关联用户ID)
* @param userId 用户ID(必须是已注册用户)
* @return 会话ID(唯一标识)
*/
public String createSession(String userId) {
// 校验用户是否存在
if (userManager.getUserRole(userId) == null) {
throw new RuntimeException("用户不存在:" + userId);
}
// 生成唯一会话ID(UUID+用户ID前缀,确保唯一性)
String sessionId = "session-" + userId + "-" + UUID.randomUUID().toString().replace("-", "");
// 创建初始短期记忆(空上下文,用于后续存储交互记录)
MemoryEntity shortMemory = new MemoryEntity(
sessionId, userId, MemoryEntity.MemoryType.SHORT, "{}"
);
redisMemoryStorage.addMemory(shortMemory);
System.out.println("会话创建成功:" + sessionId + "(关联用户:" + userId + ")");
return sessionId;
}
/**
* 存储会话记忆(自动区分短期和长期记忆)
* @param sessionId 会话ID
* @param userId 用户ID
* @param content 记忆内容(JSON格式)
* @param isLongTerm 是否为长期记忆
*/
public void storeMemory(String sessionId, String userId, String content, boolean isLongTerm) {
// 校验会话是否存在(查询短期记忆,判断会话是否有效)
List<MemoryEntity> shortMemoryList = redisMemoryStorage.getMemoryBySessionId(sessionId, 1, 1);
if (shortMemoryList.isEmpty()) {
throw new RuntimeException("会话不存在或已过期:" + sessionId);
}
MemoryEntity.MemoryType memoryType = isLongTerm ? MemoryEntity.MemoryType.LONG : MemoryEntity.MemoryType.SHORT;
MemoryEntity memoryEntity = new MemoryEntity(sessionId, userId, memoryType, content);
// 根据记忆类型选择存储方案
if (isLongTerm) {
mysqlMemoryStorage.addMemory(memoryEntity);
} else {
// 短期记忆更新(覆盖原有内容)
redisMemoryStorage.updateMemory(memoryEntity);
}
}
/**
* 恢复会话记忆(根据会话ID获取所有记忆,包括短期和长期)
* @param sessionId 会话ID
* @return 会话记忆列表(短期记忆在前,长期记忆在后)
*/
public List<MemoryEntity> restoreSession(String sessionId) {
// 1. 获取短期记忆(当前会话的临时上下文)
List<MemoryEntity> shortMemoryList = redisMemoryStorage.getMemoryBySessionId(sessionId, 1, 1);
// 2. 获取长期记忆(该会话的历史记录)
List<MemoryEntity> longMemoryList = mysqlMemoryStorage.getMemoryBySessionId(sessionId, 1, 100);
// 3. 合并记忆列表(短期记忆在前,长期记忆在后)
shortMemoryList.addAll(longMemoryList);
return shortMemoryList;
}
/**
* 删除会话(同时删除短期和长期记忆)
* @param sessionId 会话ID
* @param userId 用户ID(仅会话所属用户或管理员可删除)
*/
public void deleteSession(String sessionId, String userId) {
// 校验权限:仅会话所属用户或管理员可删除
List<MemoryEntity> memoryList = redisMemoryStorage.getMemoryBySessionId(sessionId, 1, 1);
if (memoryList.isEmpty()) {
throw new RuntimeException("会话不存在或已过期:" + sessionId);
}
String sessionUserId = memoryList.get(0).getUserId();
boolean isAdmin = userManager.getUserRole(userId) == UserManager.UserRole.ADMIN;
if (!sessionUserId.equals(userId) && !isAdmin) {
throw new RuntimeException("权限不足:仅会话所属用户或管理员可删除会话");
}
// 同时删除短期和长期记忆
redisMemoryStorage.deleteMemoryBySessionId(sessionId);
mysqlMemoryStorage.deleteMemoryBySessionId(sessionId);
System.out.println("会话删除成功:" + sessionId);
}
/**
* 获取用户的所有会话(跨设备同步核心方法)
* @param userId 用户ID
* @return 会话ID列表(该用户的所有有效会话)
*/
public List<String> getUserSessions(String userId) {
// 从MySQL中查询该用户的所有长期记忆,提取会话ID(去重)
String sql = "SELECT DISTINCT session_id FROM memory WHERE user_id = ? AND memory_type = ?";
return jdbcTemplate.queryForList(
sql,
String.class,
userId,
MemoryEntity.MemoryType.LONG.getTypeCode()
);
}
// 注入JdbcTemplate(用于查询用户会话)
@Resource
private JdbcTemplate jdbcTemplate;
}
五、实操练习:会话记忆持久化完整测试(可运行)
结合本节课所学,我们实现会话记忆持久化的完整测试,模拟“创建会话→存储记忆→会话中断恢复→跨设备同步→删除会话”全流程,验证记忆持久化、会话恢复和跨设备同步的有效性,确保代码可直接运行。
1. 测试环境准备
(1)依赖准备:在pom.xml中添加Redis、MySQL、Spring JDBC相关依赖(Claude Code默认依赖);
(2)数据库准备:创建memory表(MySQL),表结构如下(与MemoryEntity对应):
CREATE TABLE `memory` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`session_id` varchar(100) NOT NULL COMMENT '会话ID',
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
`memory_type` int(11) NOT NULL COMMENT '记忆类型:1=短期,2=长期',
`content` text NOT NULL COMMENT '记忆内容(JSON格式)',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间',
PRIMARY KEY (`id`),
KEY `idx_session_id` (`session_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话记忆表';
(3)配置准备:在application.properties中配置Redis和MySQL连接信息:
// MySQL配置
spring.datasource.url=jdbc:mysql://localhost:3306/claude_code?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
// Redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
2. 测试类实现(MemoryPersistenceTest)
package com.claudecode.test;
import com.claudecode.core.UserManager;
import com.claudecode.memory.MemoryEntity;
import com.claudecode.memory.SessionManager;
import com.alibaba.fastjson.JSON;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
import java.util.Map;
/**
* 会话记忆持久化测试:验证会话创建、记忆存储、会话恢复、跨设备同步、会话删除
*/
public class MemoryPersistenceTest {
public static void main(String[] args) {
// 1. 初始化Spring上下文(加载依赖组件)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.claudecode");
context.refresh();
// 2. 获取核心组件
SessionManager sessionManager = context.getBean(SessionManager.class);
UserManager userManager = UserManager.getInstance();
// 3. 准备测试用户(使用第06课注册的开发者用户)
String userId = "dev-001"; // 开发者用户ID(提前注册)
// 4. 测试1:创建会话
System.out.println("=== 测试1:创建会话 ===");
String sessionId = sessionManager.createSession(userId);
// 5. 测试2:存储记忆(短期+长期)
System.out.println("\n=== 测试2:存储记忆 ===");
// 存储短期记忆(当前会话的临时上下文:用户输入+Agent响应)
Map<String, Object> shortContent = Map.of(
"userInput", "帮我写一个Java工具类",
"agentResponse", "好的,我将为你编写一个文件操作工具类,包含读取和写入功能"
);
sessionManager.storeMemory(sessionId, userId, JSON.toJSONString(shortContent), false);
// 存储长期记忆(历史会话记录:工具调用结果)
Map<String, Object> longContent = Map.of(
"userInput", "帮我写一个Java工具类",
"agentResponse", "好的,我将为你编写一个文件操作工具类,包含读取和写入功能",
"toolCall", "调用FileOperationTool工具,执行write操作",
"toolResult", "文件操作工具类编写完成,已保存到test.txt"
);
sessionManager.storeMemory(sessionId, userId, JSON.toJSONString(longContent), true);
// 6. 测试3:会话中断恢复(模拟Agent重启,重新获取会话记忆)
System.out.println("\n=== 测试3:会话中断恢复 ===");
List<MemoryEntity> restoredMemory = sessionManager.restoreSession(sessionId);
System.out.println("恢复的会话记忆条数:" + restoredMemory.size());
restoredMemory.forEach(memory -> {
System.out.println("记忆类型:" + memory.getMemoryType().getTypeName());
System.out.println("记忆内容:" + memory.getContent() + "\n");
});
// 7. 测试4:跨设备同步(获取该用户的所有会话)
System.out.println("\n=== 测试4:跨设备同步 ===");
List<String> userSessions = sessionManager.getUserSessions(userId);
System.out.println("该用户的所有会话:" + userSessions);
// 8. 测试5:删除会话(同时删除短期和长期记忆)
System.out.println("\n=== 测试5:删除会话 ===");
sessionManager.deleteSession(sessionId, userId);
// 验证会话是否删除(查询记忆,应返回空)
List<MemoryEntity> deletedMemory = sessionManager.restoreSession(sessionId);
System.out.println("会话删除后,获取的记忆条数:" + deletedMemory.size());
// 关闭Spring上下文
context.close();
}
}
3. 测试结果说明
运行测试类后,控制台会依次输出以下测试结果,验证会话记忆持久化系统的核心功能:
(1)创建会话:成功生成唯一会话ID,关联用户ID,创建初始短期记忆;
(2)存储记忆:成功将短期记忆存入Redis、长期记忆存入MySQL,区分清晰;
(3)会话中断恢复:成功通过会话ID获取短期和长期记忆,恢复会话上下文,实现会话中断后继续任务;
(4)跨设备同步:成功获取该用户的所有会话ID,实现跨设备会话同步;
(5)删除会话:成功删除会话,同时删除Redis中的短期记忆和MySQL中的长期记忆,验证会话删除功能的有效性。
测试结果完全符合工业级会话记忆持久化需求,验证了系统的正确性、可靠性和可扩展性。
六、关键避坑点与实操建议
1. 避坑点(工业级开发必看)
- 会话ID唯一性:必须确保会话ID唯一,建议采用“用户ID+UUID”的格式,避免不同用户的会话ID冲突;
- 记忆内容序列化:记忆内容需采用JSON格式序列化存储,避免直接存储对象,防止序列化异常和数据错乱;
- 短期记忆过期时间:短期记忆需设置合理的过期时间(如1小时),避免Redis内存溢出;
- 事务控制:长期记忆的存储、更新、删除操作需添加事务控制,避免数据部分成功、部分丢失,确保数据一致性;
- 权限校验:会话删除、跨设备同步等操作需添加权限校验,仅会话所属用户或管理员可操作,防止数据泄露。
2. 实操建议
- 记忆压缩:长期记忆内容可采用Gzip压缩后存储,减少数据库存储占用,尤其是高频交互场景;
- 缓存优化:短期记忆缓存可添加本地缓存(如Caffeine),减少Redis查询次数,进一步提升读写性能;
- 过期清理:定期清理过期的短期记忆和长期历史记忆(如3个月前的记忆),释放存储资源,可通过定时任务实现;
- 多存储适配:实际生产环境可根据需求,扩展MongoDB等存储方案,通过MemoryStorage接口实现无缝切换;
- 监控告警:添加Redis和MySQL的监控告警,当存储异常(如Redis宕机、MySQL连接失败)时,及时告警并切换到备用存储方案。
七、本课重点总结
1. 会话记忆持久化是工业级Agent的必备功能,核心价值是实现会话中断恢复、跨设备同步,提升用户体验和系统可靠性;
2. 核心架构采用“缓存+数据库”双层存储,Redis存储短期记忆(高性能),MySQL存储长期记忆(高可靠),兼顾性能和安全性;
3. 核心组件作用:MemoryEntity封装记忆数据,MemoryStorage提供统一存储接口,SessionManager管理会话生命周期,协调记忆存储;
4. 实操关键:会话ID唯一、记忆序列化、事务控制、权限校验、过期清理,这五点是工业级会话记忆持久化系统的核心要求。
下节课预告
第08课:Agent异常处理与容错机制设计——如何应对工具调用失败、LLM响应异常、系统宕机,对应Claude Code的ExceptionHandler和RetryManager源码解析。
更多推荐



所有评论(0)