Redisson 延迟队列与分布式锁迁移记录
为提高系统调度精度、解耦并增强架构可靠性,本次进行彻底的 Redisson 改造。
本文目录 7 节
背景说明
原系统内存在两套冗余的比赛状态更新机制:
- 基于 Spring
TaskScheduler的内存精确调度(附带重启时的全量重载恢复)。 - 基于
ContestStatusScheduler每 30 秒轮询数据库的全表扫描兜底。
同时原有 ContestSchedulerService 中的 3 处分布式锁(用于控制并发修改比赛状态)都使用了手写的 SETNX 模式,且解锁方式非原子操作,存在安全隐患。
为提高系统调度精度、解耦并增强架构可靠性,本次进行彻底的 Redisson 改造。
具体变动点
1. 依赖变更
- 在
pom.xml中引入redisson-spring-boot-starter。目前直接复用已有的 Spring Redis(spring.data.redis)配置即可,开箱即用。
2. 核心调度机制重构 (延迟队列)
- 取消轮询兜底:彻底删除了
ContestStatusScheduler及其在ContestServiceImpl和拦截层中的updateContestStatuses轮询方法。 - 引入 RDelayedQueue:在
ContestSchedulerService的scheduleContest方法中,使用 Redisson 的RDelayedQueue。比赛每次创建/修改时,会比对startTime和endTime,计算延迟毫秒数后投递START:{id}和END:{id}消息。 - 并发消费者:新建
ContestDelayedQueueConsumer后台轮询组件。通过原生的RBlockingQueue.take()接管所有过期事件处理,并路由执行后续操作。 - 安全拦截校验(取消任务特性):因原生消息不易摘除旧记录,采用“消费时乐观比对时间”策略:消费者被唤醒时去数据库查询,如果
startTime被延后导致在当前还不应该发生,直接无视该条过期消息(新的有效消息已经在修改时被投递入队)。
3. 分布式锁升级 (RLock)
- 原子性增强:在
ContestSchedulerService#startContest等核心操作中,将原手写的setIfAbsent/UUIDtoken 逻辑全部移除,改为redissonClient.getLock()。 - 防误解锁机制:直接使用
lock.isHeldByCurrentThread()。 - 看门狗续期应用:由于
endContest需要处理复杂的统分排行榜固化更新,采用了lock.tryLock(0, -1, TimeUnit.SECONDS)的机制开启 Redisson Watchdog,确保长时间运行的排行榜计算不会导致分布式锁自动过期剥夺。
4. 冗余重启流程优化
- 原
ContestRecoveryInitializer每次服务重启都要轮询全表的NOT_STARTED/RUNNING注入调度器。因为现在基于 Redis 持久化存储调度队列,这一块调度器重现逻辑被精简。当前只需关注由于未处理完即宕机造成的ENDED落库失败异常恢复。
适用场景
本次更新让 OJ 系统在高压环境或多实例部署下可以完全依赖 Redis 进行强精准状态控制,免除了 CPU 密集型的全表扫行为,同时化解了 SETNX 在极端场景下的隐患。
评论
无需登录,审核通过后公开。只有博主可以回复。
正在加载…
已公开的评论