当前位置: 首页 > news >正文

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

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

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

一:定义一个定时任务

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

二:数据库查询

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

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

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

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语句

     SELECT * FROM card_game WHERE starttime >= ? AND starttime <= ? AND status = 0

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

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

(1)活动基本信息存储

redisUtil.set(RedisKeys.INFO + game.getId(), game, -1);

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

(2)活动策略信息

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

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到奖品名字的映射,过期时间设置为活动结束的时候

// 计算缓存过期时间(活动结束后失效)
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);
}

@Component
public class GameTask {private final static Logger log = LoggerFactory.getLogger(GameTask.class);@Autowiredprivate CardGameService gameService;@Autowiredprivate CardGameProductService gameProductService;@Autowiredprivate CardGameRulesService gameRulesService;@Autowiredprivate GameLoadService gameLoadService;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate CardGameProductService cardGameProductService;@Autowiredprivate 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);// 将令牌列表存入RedisredisUtil.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());// 将完整信息存入 RedisredisUtil.set(redisKey, product, expireTime);}log.info(" 奖品映射信息已缓存,共{}个奖品", productList.size());}} catch (Exception e) {log.error("缓存预热任务执行异常", e);}log.info("===== 缓存预热任务执行完成 =====");}
}

http://www.dtcms.com/a/524142.html

相关文章:

  • Linux下查看系统启动时间、运行时间
  • Linux中子系统注册subsystem_register等函数的实现
  • MFC应用程序,工作线程学习记录
  • 在什么网站做公司人员增减wordpress主题标签
  • 广州新塘网站制作推广网站服务器检测
  • 金仓多模数据库:电子证照系统国产化替代MongoDB的优选方案
  • SQL之键与约束——数据库设计的基石与数据完整性的守护者
  • 百度网站收录查询地址保定网站推广多少钱
  • 移动端网站如何开发一呼百应网
  • Spring Boot集合RabbitMQ
  • 傻瓜式大型网站开发工具金融 网站 源码
  • 精准与安全并重!NHVOC-1 (C) 型便携式 VOCs 分析仪(PID + 催化氧化 - NDIR)深度解析
  • WPF ComboBox 样式
  • paddlenlp 3.x 版本使用uie-m-base报错找不到 static/inference.pdmodel
  • 郑州市有做网站的吗wordpress如何设置点击直接下载
  • 深度学习打卡第TR5周:Transformer实战:文本分类
  • 一个强大的开源OCR工具,基于DeepSeek OCR
  • 【AI工具】Lyra超级元提示词原文分享:颠覆AI交互逻辑的「提问式」优化工具
  • 企业级表单与文件上传统一管理方案
  • 报错解决:IEEE latex模版中thanks不显示 隶属关系 / 邮箱不显示
  • 第四章:向量数据库:解锁Embeddings价值的钥匙
  • 微信的微网站模板下载wordpress 后台502
  • 基于JavaWeb技术的在线考试系统设计与实现
  • Function Calling VS MCP
  • 找公司网站建设销售网页
  • C++仿muduo库高并发服务器项目:Channel模块
  • 网站开发前端php 后端python张家界seo
  • [特殊字符]兰亭妙微审美积累|总结三个情感化设计细节✨
  • 【数列求和】
  • 第一章-第二节-Cursor IDE与MCP集成.md