后端定时过期方案选型
最近有个需求:让智能机器人在用户 1 hour 内无活跃消息后,自动关闭当前用户会话,避免无效的上下文对用户的下次提问造成干扰。这里只涉及到后端强制过期清空上下文,前端是否需要有变化/通知基于业务场景去设计就好。我主要是想到了如下三种方案并分析了一下它们的优劣。
定时任务扫表更新状态
分析
创建一个定时任务,每个小时跑一次,每次游标分批捞出会话表中 50 条活跃的会话,check一下该会话关联的 最新消息是否在 1 hour 内,没有的话就把会话状态设置为失效。
优点
- 每小时执行,天然具有重试逻辑,并且每次执行会过滤掉已经失效的会话
- 异步执行,在原代码中不存在耦合逻辑,改造量小
缺点
- 会加重数据库读负担,但是在主从分离的场景下,优先读从库,并且考虑到当前场景的新增会话体量较小,系统负载可接受
- 需要控制分布式场景下单实例执行,考虑当前公司有比较成熟的分布式任务框架,市面上也有类似 Quartz 这种分布式任务框架,可以直接使用
- 会话过期逻辑相对复杂,需要保证编码质量,考虑分批游标的方式减轻内存/CPU压力
Redis key 过期事件监听
分析
新创建会话时设置一个 key = conversationId 的 redis key,expiration time = 1hour,每次有会话关联的消息更新/新增时需要重置其关联的会话 redis key 的 expiration time = 1 hour
优点
- 没有很重地会话失效逻辑,收到 key 过期事件就将对应的会话失效
- 不会加重数据库负担
缺点
- 代码中需要在 insert 会话、create/update 消息处耦合 redis 逻辑,改造量大
- 频繁更新 redis 性能较差
- Redis 过期事件是在服务器删除 key 时发出的,不是 key 过期就会直接发出;Redis 惰性删除 + 过期删除的机制会导致时效性差,删除 key 的时机不一定是 key 过期的时机
- 过期事件监听丢失无法重试
RocketMQ 延迟消息推送
分析
新创建会话时延迟 1 hour 发送一条消息,消费者 1hour 后收到消息会校验该会话的状态,每次有会话关联的消息更新/新增时需要再发送一条其关联的会话的相同延迟消息
优点
- 消息有重试机制,稳定性较高
- 不会加重数据库负担
缺点
- 代码中需要在 insert 会话、create/update 消息处耦合 MQ 逻辑,改造量大
- 每次 insert 会话、create/update 消息都需要发送 MQ,消息冗余
- 接收到消息的会话过期逻辑比较复杂,需要校验状态,也需要做消息幂等处理
- 会有许多和最终关闭会话无关的消息发送出来
最终选型
最终选型为 定时任务扫表更新状态
因为其天然具有重试逻辑,并且不会在原有代码中耦合逻辑,改造成本比较小,此外还没有过多无效消息被发出来。