记录自己写项目的第三天,springbot+redis+rabbitma高并发项目

今天实现了缓存预热这个接口任务,文章末端附完整代码

缓存预热的目的就是在活动开始之前,把数据提前存入到redis 中,避免活动开始时,大量数据请求打到数据库

一:定义一个定时任务

每分钟对活动的开始情况进行检测一次,如果有活动开始,就通过下面的操作将他们存入到redis中

二:数据库查询

首先设计咱们查询的时间范围,未来一分钟开始的活动(当前时间 ≤ 活动开始时间 ≤ 未来 1 分钟)

java 复制代码
Date now = new Date();
now = DateUtils.truncate(now, Calendar.SECOND); // 截断毫秒,统一为秒级精度
Date nextOneMinute = DateUtils.addMinutes(now, 1); // 未来1分钟的时间

然后使用MP中的查询,查询时间在这个区间内的活动展开情况

java 复制代码
QueryWrapper<CardGame> gameQuery = new QueryWrapper<>();
gameQuery.ge("starttime", now)            // 开始时间 ≥ 当前时间
        .le("starttime", nextOneMinute)   // 开始时间 ≤ 未来1分钟
        .eq("status", 0);                 // 状态为0(新建,未开始)
List<CardGame> pendingGames = gameService.list(gameQuery);

最后一行的List方法相当于下面的sql语句

sql 复制代码
     SELECT * FROM card_game 
     WHERE starttime >= ? 
       AND starttime <= ? 
       AND status = 0
     

三:遍历活动,将活动的数据缓存到redis中

活动的数据包括:1活动基本信息,活动策略信息,抽奖令牌桶,奖品映射信息

(1)活动基本信息存储

java 复制代码
redisUtil.set(RedisKeys.INFO + game.getId(), game, -1);

这里要注意的点就是将这些信息储存成永不过期,因为这种信息基本不变,变了可以直接覆盖原来的信息,所以可以存储成永不过期

(2)活动策略信息

因为活动的服务对象是不同等级的成员,不同等级的成员对应不同的参加次数和中奖次数

java 复制代码
QueryWrapper<CardGameRules> rulesQuery = new QueryWrapper<>();
rulesQuery.eq("gameid", game.getId());
List<CardGameRules> rulesList = gameRulesService.list(rulesQuery);

for (CardGameRules rule : rulesList) {
    String userLevelKey = String.valueOf(rule.getUserlevel()); // 用户等级(如:1、2、3)
    // 存储不同用户等级的中奖次数限制
    redisUtil.hset(RedisKeys.MAXGOAL + game.getId(), userLevelKey, rule.getGoalTimes());
    // 存储不同用户等级的参与次数限制
    redisUtil.hset(RedisKeys.MAXENTER + game.getId(), userLevelKey, rule.getEnterTimes());
    // 存储不同用户等级的中奖概率
    redisUtil.hset(RedisKeys.RANDOMRATE + game.getId(), userLevelKey, rule.getRandomRate());
}

(3)抽奖令牌桶

这一步也是比较难想到的一步,下面是它设计的目的和设计方式

  • 核心目的:生成 "抽奖令牌",用于控制抽奖的时间和次数(每个令牌对应一次中奖机会)。
  • 令牌设计:
    • 基础:活动开始到结束期间的随机时间戳(确保令牌在活动有效期内)。
    • 唯一性:时间戳 + 3 位随机数(避免同一毫秒生成重复令牌)。
  • 存储:令牌列表按时间排序后存入 Redis(键:game:tokens:1001),方便后续抽奖时按时间顺序取出(确保公平性)。

(4)缓存奖品映射信息

实现id到奖品名字的映射,过期时间设置为活动结束的时候

java 复制代码
// 计算缓存过期时间(活动结束后失效)
long expireTime = (endTime.getTime() - now.getTime()) / 1000; // 秒级

for (int i = 0; i < tokenList.size(); i++) {
    // 随机选择一个奖品与令牌关联(注:此处逻辑可能需根据业务调整,通常应绑定固定奖品)
    CardGameProduct randomProduct = productList.get(random.nextInt(productList.size()));
    Long token = tokenList.get(i);
    String redisKey = RedisKeys.TOKEN + game.getId() + "_" + token; // 键:game:token:1001_xxxxxx

    // 查询奖品完整信息
    CardProduct product = cardProductService.getById(randomProduct.getProductid());
    // 缓存令牌与奖品的映射
    redisUtil.set(redisKey, product, expireTime);
}
java 复制代码
@Component
public class GameTask {
    private final static Logger log = LoggerFactory.getLogger(GameTask.class);
    @Autowired
    private CardGameService gameService;
    @Autowired
    private CardGameProductService gameProductService;
    @Autowired
    private CardGameRulesService gameRulesService;
    @Autowired
    private GameLoadService gameLoadService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private CardGameProductService cardGameProductService;
    @Autowired
    private CardProductService cardProductService;




    @Scheduled(cron = "0 * * * * ?")
    public void execute() {
        log.info("===== 开始执行缓存预热任务,当前时间:{} =====", new Date());

        try {
          //  1. 截断毫秒,统一时间精度为秒级 now = DateUtils.truncate(now, Calendar.SECOND);
            Date now = new Date();

            now = DateUtils.truncate(now, Calendar.SECOND);
            // 2. 扩大查询范围到未来1分钟
            Date nextOneMinute = DateUtils.addMinutes(now, 1);
            QueryWrapper<CardGame> gameQuery = new QueryWrapper<>();
            gameQuery.ge("starttime", now)            // 开始时间 ≥ 当前时间
                    .le("starttime", nextOneMinute)   // 开始时间 ≤ 未来1分钟
                    .eq("status", 0);                 // 状态为0表示"新建"
            List<CardGame> pendingGames = gameService.list(gameQuery);
            log.info("查询到{}个即将开始的活动,时间范围:{}~{},状态:0",
                    pendingGames.size(), now, nextOneMinute);

            for (CardGame game : pendingGames) {
                log.info("开始预热活动【ID:{},名称:{}】", game.getId(), game.getTitle());

                // 2. 缓存「活动基本信息」:永不过期
                redisUtil.set(RedisKeys.INFO + game.getId(), game, -1);
                log.info(" 活动基本信息已缓存");

                // 3. 缓存「活动策略信息」:按用户等级存储中奖/参与次数限制
                QueryWrapper<CardGameRules> rulesQuery = new QueryWrapper<>();
                rulesQuery.eq("gameid", game.getId());
                List<CardGameRules> rulesList = gameRulesService.list(rulesQuery);
                for (CardGameRules rule : rulesList) {
                    String userLevelKey = String.valueOf(rule.getUserlevel());
                    redisUtil.hset(RedisKeys.MAXGOAL + game.getId(), userLevelKey, rule.getGoalTimes());
                    redisUtil.hset(RedisKeys.MAXENTER + game.getId(), userLevelKey, rule.getEnterTimes());
                    redisUtil.hset(RedisKeys.RANDOMRATE + game.getId(), userLevelKey, rule.getRandomRate());
                }
                log.info(" 活动策略信息已缓存,共{}条用户等级规则", rulesList.size());

                // 4. 生成「抽奖令牌桶」:为每个奖品生成时间戳令牌
                QueryWrapper<CardGameProduct> productQuery = new QueryWrapper<>();
                productQuery.eq("gameid", game.getId());
                List<CardGameProduct> productList = gameProductService.list(productQuery);

                // 计算总的奖品数量(考虑amount字段)
                int totalProducts = 0;
                for (CardGameProduct product : productList) {
                    totalProducts += product.getAmount() != null ? product.getAmount() : 0;
                }

                if (totalProducts == 0) {
                    log.warn("活动【{}】无奖品,跳过令牌桶和奖品映射缓存", game.getId());
                    continue;
                }

                Date startTime = game.getStarttime();
                Date endTime = game.getEndtime();
                long timeDuration = endTime.getTime() - startTime.getTime();
                List<Long> tokenList = new ArrayList<>();
                Random random = new Random();

                // 为每个奖品生成对应数量的时间戳令牌
                for (CardGameProduct product : productList) {
                    int amount = product.getAmount() != null ? product.getAmount() : 0;
                    for (int i = 0; i < amount; i++) {
                        long randomTime = startTime.getTime() + random.nextInt((int) timeDuration);
                        // 生成带3位随机数的令牌
                        long token = randomTime * 1000 + random.nextInt(999);
                        tokenList.add(token);
                    }
                }

                // 对令牌进行排序
                tokenList.sort(Long::compareTo);
                // 将令牌列表存入Redis
                redisUtil.rightPushAll(RedisKeys.TOKENS + game.getId(), tokenList);
                log.info(" 抽奖令牌桶已生成并缓存,共{}个令牌", tokenList.size());

                // 5. 缓存「奖品映射信息」
                long expireTime = (endTime.getTime() - now.getTime()) / 1000; // 过期时间单位为秒
                for (int i = 0; i < tokenList.size(); i++) {
                    // 随机选择一个奖品与令牌关联
                    CardGameProduct randomProduct = productList.get(random.nextInt(productList.size()));
                    Long token = tokenList.get(i);
                    String redisKey = RedisKeys.TOKEN + game.getId() + "_" + token;

                    // 查询对应的 CardProduct 完整信息
                    CardProduct product = cardProductService.getById(randomProduct.getProductid());

                    // 将完整信息存入 Redis
                    redisUtil.set(redisKey, product, expireTime);
                }
                log.info(" 奖品映射信息已缓存,共{}个奖品", productList.size());
            }

        } catch (Exception e) {
            log.error("缓存预热任务执行异常", e);
        }
        log.info("===== 缓存预热任务执行完成 =====");
    }
}
相关推荐
SepstoneTang3 小时前
前端新手入门-HBuilder工具安装
html·html5·1024程序员节
WebKoalaBoy3 小时前
前端埋点学习
1024程序员节
羑悻的小杀马特3 小时前
告别内网限制!用StirlingPDF+cpolar打造可远程访问的PDF工具站
pdf·cpolar·1024程序员节·stirlingpdf
鸽鸽程序猿3 小时前
【算法】【动态规划】斐波那契数模型
算法·动态规划·1024程序员节
就是ping不通的蛋黄派3 小时前
CentOS7 部署主从复制MariaDB数据库
网络·1024程序员节
独行soc3 小时前
2025年渗透测试面试题总结-215(题目+回答)
网络·安全·web安全·adb·渗透测试·1024程序员节·安全狮
暖阳之下3 小时前
学习周报十九
学习·多模态·1024程序员节
幻世界3 小时前
【Unity开发小技巧】安卓ADB环境配置
1024程序员节
☆璇3 小时前
【Linux】应用层协议HTTP
网络·网络协议·http·1024程序员节