20260626-视频播放计数Redis+定时刷库设计方案.md 5.2 KB

视频播放计数设计方案(Redis + 定时刷库)

日期:2026-06-26

一、设计背景

视频播放次数的统计需要避免"每播放一次就写一次数据库"的笨重做法,减少数据库写入压力。项目已集成 Redis,且提供了 RedisUtil 工具类和 @Scheduled 定时任务支持,采用 Redis 计数器 + 定时批量刷库 方案。

二、技术选型

组件 用途 依据
RedisUtil 播放计数自增(内存级操作,毫秒响应) 项目已集成 Redis,RedisUtil 位于 jeecg-boot-common 依赖
@Scheduled 定时将 Redis 增量刷入数据库 项目已有 UserInfoJobRedisServiceImpl@Scheduled 使用先例
MySQL/DM8 持久化存储最终播放次数 SppbVideo.playCount 字段已存在

三、整体架构

用户播放视频
    │
    ▼
PUT /spbb/sppbVideo/{videoId}/play
    │
    ▼
SppbVideoServiceImpl.recordPlay(videoId)
    │
    ├── redisUtil.get("spbb:play:count:{videoId}")  读取当前计数
    └── redisUtil.set("spbb:play:count:{videoId}", +1)  写入新计数
    │  操作耗时 ≈ 1ms,不涉及数据库写入
    │
    ┌──────────────────────────────────────────────┐
    │        @Scheduled(fixedRate = 300000)         │  每5分钟
    │          SppbVideoPlayFlushJob                │
    │                                               │
    │  1. redisUtil.scan("spbb:play:count:*")       │  扫描所有计数key
    │  2. 遍历每个 key,解析 videoId                │
    │  3. UPDATE sppb_video SET play_count += ?     │  批量更新数据库
    │  4. redisUtil.del(key)                        │  清理已落库的key
    └──────────────────────────────────────────────┘

四、涉及的文件

4.1 新增文件

文件 说明
jeecg-boot/.../spbb/video/job/SppbVideoPlayFlushJob.java 定时刷库任务,每 5 分钟执行一次,将 Redis 中的播放计数增量批量更新到 sppb_video.play_count 字段

4.2 修改文件

文件 操作 原因
jeecg-boot/.../spbb/video/service/ISppbVideoService.java 新增 recordPlay(String videoId) 方法声明 Service 接口层暴露播放计数接口
jeecg-boot/.../spbb/video/service/impl/SppbVideoServiceImpl.java 新增 RedisUtil 注入 + recordPlay 方法实现 使用 Redis 计数器自增播放次数
jeecg-boot/.../spbb/video/controller/SppbVideoController.java 新增 PUT /{videoId}/play 接口 对外提供播放计数 API
jeecg-boot-base-core/.../shiro/ShiroConfig.java 新增 /spbb/sppbVideo/*/play 至 anon 白名单 外部系统免认证调用

五、详细实现

5.1 播放计数接口

// Controller 层 - 接收播放请求
@PutMapping(value = "/{videoId}/play")
public Result<String> recordPlay(@PathVariable String videoId) {
    sppbVideoService.recordPlay(videoId);
    return Result.OK("ok");
}

// Service 层 - Redis 计数器自增(毫秒级,不写数据库)
@Override
public void recordPlay(String videoId) {
    String key = "spbb:play:count:" + videoId;
    Integer count = oConvertUtils.getInt(redisUtil.get(key), 0);
    redisUtil.set(key, count + 1);
}

5.2 定时刷库任务

@Component
@Slf4j
public class SppbVideoPlayFlushJob {

    @Scheduled(fixedRate = 300000) // 每5分钟
    public void flushPlayCount() {
        // 1. 扫描所有播放计数 key
        Collection<String> keys = redisUtil.scan("spbb:play:count:*");
        // 2. 逐 key 处理:解析 videoId → 获取增量 → 更新 DB → 删除 key
        for (String key : keys) {
            String videoId = key.substring("spbb:play:count:".length());
            Integer increment = oConvertUtils.getInt(redisUtil.get(key), 0);
            SppbVideo video = sppbVideoService.getById(videoId);
            if (video != null) {
                video.setPlayCount(video.getPlayCount() == null
                    ? increment : video.getPlayCount() + increment);
                sppbVideoService.updateById(video);
            }
            redisUtil.del(key);
        }
    }
}

六、关键设计决策

决策 选择 理由
计数存储 Redis String(非 List/Set) 单 key 单值,读写简单,scan 可批量扫描
原子性 非原子自增(read + set) RedisUtilincr 方法,播放次数非关键数据,少量并发误差可接受
刷库频率 5 分钟 平衡实时性与数据库压力;可在配置中调整
刷库后处理 删除 key 避免 key 无限堆积,下一周期重新计数
服务重启恢复 Redis RDB/AOF 持久化 计数不丢失;若丢失也仅是少量播放次数,影响极小

七、调用方式

PUT /jeecg-boot/spbb/sppbVideo/{videoId}/play
  • 无需认证(已加入 Shiro anon 白名单)
  • 无请求体
  • 响应:{"success": true, "code": 200, "message": "ok"}
  • 前端可在视频播放结束时调用此接口