比赛排行榜赛后链路优化方案
1. 赛后结算链路解耦为:Redisson 延时触发 -> 发送 MQ 事件 -> MQ 消费者重建最终榜单。 2. 排行查询补齐 Cache-Aside 回填:Redis 未命中且 DB 命中时,将结果写回 Redis,避免持续回源数据库。
更新于 2026.08.07
本文目录 16 节
1. 业务背景与目标
本方案聚焦比赛结束后的两个核心瓶颈:
- 赛后结算链路解耦为:Redisson 延时触发 -> 发送 MQ 事件 -> MQ 消费者重建最终榜单。
- 排行查询补齐 Cache-Aside 回填:Redis 未命中且 DB 命中时,将结果写回 Redis,避免持续回源数据库。
必须满足的约束:
- 重建期间不删除 Redis 中现有排行榜数据与用户题目详情数据。
- 技术栈保持不变:Spring Boot 3.x + Redis + Redisson + RabbitMQ + MySQL。
2. 当前现状(As-Is)
基于当前后端实现可观察到:
- 已存在 Redisson 延时队列并可触发比赛结束事件。
- 当前赛后处理由调度路径直接调用排行榜结算服务,未经过 MQ 解耦。
- 已结束比赛查询路径为 Redis 优先,未命中后回源 DB。
- DB 回源结果直接返回,没有写回 Redis。
- 结算完成后会在事务提交后删除 Redis 排行 key 与用户题目详情 key。
在高并发下的影响:
- 调度线程可能被重型结算逻辑长期占用。
- 热门结束比赛会因重复 miss 回源导致 DB 持续高压。
- 重建窗口内若数据源切换不当,可能出现读链路抖动。
3. 目标架构(To-Be)
3.1 组件级架构图
3.2 赛后重建时序
3.3 已结束榜单查询时序(Cache-Aside)
4. 核心设计决策
4.1 重建期间不删在线数据
采用版本化 key + 指针切换,替代“先删后建”:
- 构建榜 key:contest:rank:build:{contestId}:{version}
- 构建详情 key:contest:detail:build:{contestId}:{version}:{userId}
- 活跃版本指针:contest:rank:active:version:{contestId}
规则:
- 查询始终读取活跃版本指针指向的数据。
- 消费者在后台构建新版本。
- DB 结算成功后再原子切换指针。
- 老版本通过 TTL 或延迟清理任务回收。
效果:重建期间无榜单空窗,不需要强制删除在线缓存。
4.2 赛后结算 MQ 解耦
引入专用事件:
- 事件名:ContestFinalizeEvent
- 唯一标识:taskId + contestId + expectedEndTime + retryCount
- 队列:contest.finalize.queue(带 DLQ)
保障:
- 至少一次投递语义 + 消费幂等。
- 调度线程保持轻量、可预测。
- 重试与死信可观测、可告警。
4.3 DB 回源后的缓存回填策略
针对已结束比赛:
- Redis miss -> DB fallback。
- 先将 DB 结果返回给用户。
- 通过加锁 + 双检触发回填:
- 锁 key:contest:rank:backfill:lock:{contestId}
- 锁 TTL:10s 到 30s
- 回填方式:
- 小比赛同步回填;
- 大比赛通过 MQ 异步回填。
建议阈值:
- 同步回填阈值:参赛人数 <= 2000。
- 异步回填阈值:参赛人数 > 2000。
4.4 结算状态机
定义显式状态,提升可观测性与查询保护能力:
- 0:IDLE
- 1:QUEUED
- 2:BUILDING
- 3:DONE
- 4:FAILED
查询策略:
- BUILDING 且已有 active pointer:读取 active 版本数据。
- BUILDING 且无 active pointer:返回统一“排行榜生成中”响应。
5. 高并发与性能权衡
5.1 预期收益
- 结束调度线程耗时:由重型同步逻辑降为 $O(1)$ 级事件发布。
- 已结束榜单 DB 回源 QPS:通过回填 + singleflight 显著下降。
- 重建期间读稳定性:通过版本指针切换显著提升。
5.2 Trade-offs 分析
-
收益:降低延迟与 DB 压力。 代价:组件与状态流转变多(MQ + 状态机 + 版本化 key)。
-
收益:重建期间无删除空窗。 代价:版本重叠期 Redis 会出现短时双份内存占用。
-
收益:链路解耦更彻底、重试可观测。 代价:必须严格落实幂等治理与 DLQ 运维策略。
5.3 建议 SLO 指标
- 榜单查询 P95 <= 120ms(Redis 命中)
- 榜单查询 P95 <= 350ms(DB fallback 路径)
- 5 万提交量下赛后结算完成时间 <= 3 分钟
- 重复结算副作用 = 0(幂等可证明)
6. 关键风险与控制措施
-
重复发布/重复消费。 控制:taskId 幂等键 + DB 唯一约束 + 状态 CAS 更新。
-
回填风暴(击穿放大)。 控制:短锁 + 双检 + 异步回填聚合。
-
DB 未成功即切换指针。 控制:严格顺序,必须 DB 成功后再切换指针。
-
版本重叠导致 Redis 内存上涨。 控制:仅保留 N=2 个版本并设置过期策略。
评论
无需登录,审核通过后公开。只有博主可以回复。
正在加载…
已公开的评论