非遗AI对话系统架构升级实战
从内存到持久化:我的非遗 AI 对话系统架构升级实录
1. 背景与痛点(Why)
初始状态:
最开始为了快速验证 AI 对接功能,我把对话历史直接存在 List<Map> 中,依托于 InMemoryChatMemory 的内存变量。这种方案在单机开发时非常爽快,但随着项目模拟多用户场景,三个致命问题暴露无遗:
- 数据“裸奔”与隐私风险:内存中没有严格的用户隔离,光靠一个 sessionId 难以完全防止不同用户间的隐私数据越界访问。
- 重启即丢失:服务一旦重启或部署,用户聊了一晚上的非遗知识瞬间清零,毫无持久性可言。
- 扩容死穴:如果未来做多实例部署,用户请求落到不同服务器,由于内存不共享,根本查不到之前的对话上下文。
- OOM 隐患:随着对话增多,内存占用直线飙升,随时可能触发 OOM(内存溢出)。
决策:
必须引入 MySQL 做数据持久化(兜底),引入 Redis 做热点加速(性能),彻底重构存储层。
2. 方案设计(Design)
2.1 业务模型升级
首先支持多模态对话(文本 + 图文),并重构会话模型:
- 强绑定用户:在会话元数据中强制增加
user_id,将会话与用户严格绑定,从根源杜绝恶意访问他人对话。 - 分层存储策略:
- 热数据(最近对话):存入 Redis,设置 TTL(1-2 天),利用其高性能支撑高频读写。
- 冷数据(全量归档):异步/同步落入 MySQL,用于长期留存、审计及后续的知识库推荐分析。
2.2 架构流程图
2.3 选型与表结构设计
为什么用 Redis?
对话具有极强的“时序性”和“局部性”。用户通常只关注最近几轮对话。Redis 的 List 结构天然适合存储对话流,且读写速度是毫秒级,能极大降低 DB 压力。
为什么用 MySQL?
作为“单一事实来源(Source of Truth)”。我需要记录谁(user_id)、在什么时间(create_time)、问了什么(content),以便后面做智能推荐。
核心表结构:
-
ai_chat_session(会话元数据表):记录会话维度的信息。create table ai_chat_session ( session_id varchar(64) not null primary key, user_id bigint null, -- 关键:用户绑定 current_location varchar(100) null, session_context json null, message_count int default 0, start_time datetime default CURRENT_TIMESTAMP, last_active_time datetime null, status varchar(20) default 'active', -- 软删除标记 constraint fk_user foreign key (user_id) references user (user_id) ); -- 索引优化:快速查询某用户的最近会话 create index idx_user_session on ai_chat_session (user_id asc, last_active_time desc); -
ai_chat_message(对话消息表):记录具体的问答内容。create table ai_chat_message ( chat_id bigint not null primary key, session_id varchar(64) not null, role varchar(20) not null, -- user/assistant content text null, message_type varchar(20) default 'text', tool_calls json null, metadata json null, create_time datetime default CURRENT_TIMESTAMP, constraint fk_session foreign key (session_id) references ai_chat_session (session_id) ); -- 索引优化:快速拉取某会话下的所有消息 create index idx_session_time on ai_chat_message (session_id, create_time);
3. 实施过程中的“痛苦”与解决(The Struggle)
这部分是本次重构最核心的价值,也是我踩坑最深的地方。
难点一:数据结构转换与序列化
问题:内存中是简单的 Java 对象,落库需要序列化,且 Redis 和 MySQL 的存储格式不一致。
解决:
我引入了 Jackson 的 ObjectMapper 进行统一处理。
- 定义了一个中间 DTO 对象
Msg,专门用于屏蔽底层Message对象的复杂性。 - 在写入前,将
Message转为Msg再序列化为 JSON 字符串存入 Redis List 或 MySQL 文本字段。 - 在读取时,反序列化回
Msg再还原为Message。 - 心得:不要直接把实体类扔进缓存,中间层隔离能让后续重构更容易
难点二:双写顺序与一致性陷阱(核心)
问题:先写 Redis 还是先写 MySQL?如果一边成功一边失败怎么办?
我的决策与实现:
我采用了 “先 Redis 后 MySQL” 的策略,以保证用户感知的响应速度。
-
会话索引写入逻辑 (
DatabaseChatHistoryRepository.save):@Override public void save(String type, String sessionId, Long userId) { // 1. 先写 Redis:确保用户立刻能在列表中看到新会话 redisUtils.save(type, sessionId, userId); // 2. 后写 MySQL:确保持久化 saveOrUpdateSession(sessionId, userId); } -
对话内容写入逻辑 (
RedisChatMemory.add):
为了极致性能,我决定对话详情(Message)暂时只存 Redis,不实时同步到 MySQL(通过定时任务异步归档),避免频繁 IO 阻塞 AI 回复。
新的痛点:事务边界失效
在实现删除功能时,我遇到了大坑。我使用了 Spring 的 @Transactional 注解,原本指望它能保证 Redis 和 MySQL 要么都成功,要么都失败。
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(String type, String sessionId, Long userId) {
// ⚠️ 陷阱:Redis 操作不在 Spring 事务管理范围内!
redisUtils.delete(type, sessionId, userId);
// 只有这里的 MySQL 操作受事务保护
chatSessionMapper.deleteById(...);
}
风险推演:如果 Redis 删除成功了,但后续 MySQL 更新因为权限校验失败抛出了异常,Spring 会回滚 MySQL 操作,但Redis 的数据已经删了,回滚不了! 这会导致“数据库里有记录,但缓存里没了”的数据不一致。
我的修正方案:
- 前置校验:在操作 Redis 之前,先查数据库校验权限,尽量让异常在“污染”Redis 之前就抛出。
- 承认不足与规划:我在代码注释中明确标记了此风险,并规划了后续引入**“本地消息表”或“MQ 最终一致性”**方案来彻底解决跨源事务问题。这让我明白,
@Transactional不是万能药,它管不了 Redis。
难点三:AI 辅助的得与失
实话实说:
刚开始重构时,我没有头绪然后依赖 AI 生成代码。
- AI 的坑:它生成的 Redis Key 没有规范前缀(如
project:chat:userId:),导致 Key 杂乱无章;它忽略了双写时的原子性问题,直接给出了看似完美实则有隐患的代码。 - 我的修正:
- 手动重构了所有 Key 的生成策略,统一命名空间。
- 审查了每一处双写逻辑,识别出事务失效的风险点。
- 结论:AI 能帮我写 代码,但架构决策、边界检查、异常处理必须由人来把控。这次重构让我从“调包侠”变成了真正的“设计者”。
4. 最终效果与反思(Result & Reflection)
效果
- 数据持久化:服务重启数据不再丢失,用户会话安全隔离。
- 性能提升:利用 Redis 承载高频对话读写,数据库压力显著降低。
- 架构清晰:形成了“Redis 抗流量 + MySQL 存底线”的标准后端架构。
不足与展望
- 同步写库阻塞:目前仍是同步写 MySQL,高并发下可能影响接口 RT。计划:后续学习 RocketMQ,将落库操作异步化。
- 数据一致性隐患:删除操作的跨源事务问题尚未完美解决。计划:实现“本地消息表 + 定时对账”机制,保证最终一致性。
- 历史数据归档:Redis 容量有限。计划:开发定时任务,将超过 2 天的 Redis 对话“冷备”到 MySQL,释放缓存空间。
总结
这次重构让我深刻体会到:好的架构不是设计出来的,是演进出来的。
从内存到 Redis+MySQL,不仅仅是换了个存储介质,更是对数据一致性、事务边界、性能权衡的一次深度思考。我不再盲目相信 AI 生成的代码,而是学会了审视每一个技术决策背后的代价。这或许就是工程化的魅力所在。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)