Java使用Redis ZSet恢复用户能量

阅读本篇博文前,建议先查阅本篇博文的姊妹篇

https://blog.csdn.net/shenxiaomo1688/article/details/155241107?spm=1001.2014.3001.5501

当用户进行答题,消耗一定的能量,每隔30分钟恢复一个能量值,很多时候我们会想到使用定时任务,每隔一段时间扫描有哪些用户能量低于最大能量值,然后进行恢复。但不同用户恢复能量的时间点不一样。那么我们应该如何更好地实现用户能量恢复呢。答案是使用Redis的ZSet。

实现思路:

1.当用户进行答题扣除能量后,为该用户进行订阅,列入能量恢复的用户体系中;

2.然后在定时任务中为用户进行能量恢复;

3.如果用户开通会员,或者浏览广告恢复能量后,当该用户从能量恢复的用户体系中移除出去。

这样做的好处是避免用户能量表全表扫描。下面是实现的示例代码:

java 复制代码
//用户扣除能量后,将该用户注册到用户能量恢复的订阅机制中
public void registerFullEnergyNotify(int uid, UserEnergyDTO entity, long now) {
    // 每30分钟恢复1个能量值
    public static final long RESTORE_INTERVAL_MILLIS = 30L * 60 * 1000;
	//能量值满时通知key
    public static final String ENERGY_NOTIFY_KEY = "userEnergy:full:notify";

	int energy = entity.getEnergy();
	int maxEnergy = entity.getMaxEnergy();

	if (energy >= maxEnergy) {
		// 已满,不需要提醒
		redisTemplate.opsForZSet().remove(ENERGY_NOTIFY_KEY, String.valueOf(uid));
		return;
	}

	long intervalMillis = ListeningConstant.RESTORE_INTERVAL_MILLIS;
	long needPoints = maxEnergy - energy;
	long fullRecoverAt = now + needPoints * intervalMillis;

	redisTemplate.opsForZSet().add(
			ENERGY_NOTIFY_KEY,
			String.valueOf(uid),
			fullRecoverAt
	);
}

然后在定时任务中进行能量恢复

java 复制代码
   //每5分钟检查一次
    @Scheduled(fixedDelay = 5 * 60 * 1000)
    public void energyFullNotifyTask() {
        long now = System.currentTimeMillis();

        Set<Object> uids = redisTemplate.opsForZSet().rangeByScore(ENERGY_NOTIFY_KEY, 0, now);

        if (uids == null || uids.isEmpty()) {
            //log.info("当前没有能量回满需要通知的用户");
            return;
        }

        for (Object obj : uids) {
            String uidStr = String.valueOf(obj);
            int uid = Integer.parseInt(uidStr);

            try {
                handleEnergyFull(uid);
            } catch (Exception e) {
                log.error("能量回满恢复失败 uid={}", uid, e);
                continue;
            }

            // 成功后该用户移除出能量回满的任务队列
            redisTemplate.opsForZSet().remove(ENERGY_NOTIFY_KEY, uidStr);
        }
    }

    public void handleEnergyFull(int uid) {
       //调用getEnergyInfo方法进行能量恢复
	   UserEnergyDTO dto = userEnergyService.getEnergyInfo(uid);
        //在推送前再次确认能量是否已满
        if (dto.getEnergy() < dto.getMaxEnergy()) {
            log.error("能量未满但触发推送 uid={}", uid);
            return;
        }
        //能量回满后可发送推送提醒用户

    }

前面博文使用了redis hash存储用户能量信息,本篇使用zset管理用户能量恢复。

为什么能量设计一定会用到两种结构?

典型能量系统会同时关心这 3 类问题:

  1. 当前状态

    • 当前能量值

    • 最大能量

    • 上次变化时间

  2. 时间驱动恢复

    • 每 N 分钟恢复 1 点

    • 什么时候恢复下一点

  3. 批量 / 定时处理

    • 扫描哪些用户该恢复了

    • 防止每次请求都实时算

Hash 擅长存"状态"

ZSet 擅长存"时间点 + 排序"

Hash vs ZSet 的明确分工表

维度 Hash ZSet
存什么 当前状态 时间 / 排序
是否排序
是否范围查询
单用户读写 ⭐⭐⭐⭐
批量扫描 ⭐⭐⭐⭐
是否适合能量值
相关推荐
Mr.Daozhi37 分钟前
RAG 进阶实战:跑通 Demo 后我连续翻了 6 次车,逐一修复才真正可用(含 Gradio Web 版)
前端·数据库·langchain·大模型·gradio·rag·科研工具
小程故事多_8043 分钟前
Claude Code自定义workflow skills用法
数据库·人工智能·智能体
大鹏说大话43 分钟前
SQL 排序与分组实战:解决“分组后取最新数据“
android·java·数据库
夏贰四1 小时前
数据建模工具如何筑牢数据根基?数据建模工具怎样落实标准体系?
数据库·数学建模·数据建模工具
程序猿阿伟3 小时前
《一套完整方法论:搞定图形应用的Docker镜像优化》
数据库·docker·容器
二等饼干~za8986683 小时前
geo优化源码开发搭建技术分享
大数据·网络·数据库·人工智能·音视频
数据库小学妹3 小时前
HTAP混合负载架构:如何用一个数据库同时搞定交易和分析
数据库·经验分享·架构·dba
wuxinyan1233 小时前
工业级大模型学习之路029:解决双智能体调用数据库报错问题
数据库·人工智能·python·学习·智能体
Elastic 中国社区官方博客3 小时前
Elastic 线下 Meetup 将于 2026 年 7 月 26 号下午在深圳举行
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
YL200404263 小时前
【Redis实战篇】秒杀实现方案(以优惠券秒杀为例)
数据库·redis