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

外卖霸王餐“实时试吃名额剩余量”展示:Java后端基于Redis HyperLogLog+Lua脚本实现高精度低内存的计数方案

外卖霸王餐“实时试吃名额剩余量”展示:Java后端基于Redis HyperLogLog+Lua脚本实现高精度低内存的计数方案

背景:霸王餐活动的高并发计数挑战

作为外卖霸王餐API唯一供给源头,同时也是霸王餐外卖CPS取链源头,俱美开放平台每天承载着海量用户对“试吃名额”的实时查询与抢占。在秒杀级的高并发场景下,传统的数据库计数方案(如SELECT COUNT(*))会因频繁的行锁和全表扫描导致性能急剧下降,甚至拖垮数据库。

我们需要一种既能精确统计参与人数(去重),又能以极低内存消耗支撑高并发读写的方案。Redis的HyperLogLog数据结构结合Lua脚本的原子性,成为了最佳选择。

为什么选择HyperLogLog?

HyperLogLog(HLL)是Redis提供的一种概率数据结构,专门用于基数统计(Cardinality)。

  1. 极低内存占用:无论统计多少元素,HLL在Redis中只需要约12KB内存。
  2. 高性能:添加元素和计算基数的时间复杂度均为O(1)。
  3. 适用场景:非常适合统计UV(独立访客)、参与人数等不需要绝对精确(标准误差约0.81%)的场景。

对于霸王餐活动,我们更关注“还有多少名额”而非“具体是哪几个人”,HLL完美契合。

核心设计:Lua脚本保证原子性

单纯使用PFADDPFCOUNT在高并发下可能存在竞态条件。我们需要通过Lua脚本将“判断是否已参与”、“添加用户”、“计算剩余名额”封装为一个原子操作。

Lua脚本逻辑:

  1. 检查用户是否已存在于HLL中。
  2. 若不存在,添加用户并计算当前总参与人数。
  3. 返回剩余名额(总名额 - 当前参与人数)。
Java后端实现

1. Redis配置与Lua脚本加载

packagebaodanbao.com.cn.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.core.script.DefaultRedisScript;importorg.springframework.scripting.support.ResourceScriptSource;importorg.springframework.core.io.ClassPathResource;/** * Redis脚本配置 * @author baodanbao.com.cn */@ConfigurationpublicclassRedisScriptConfig{@BeanpublicDefaultRedisScript<Long>trialCountScript(){DefaultRedisScript<Long>script=newDefaultRedisScript<>();// Lua脚本放在resources/scripts/trial_count.luascript.setScriptSource(newResourceScriptSource(newClassPathResource("scripts/trial_count.lua")));script.setResultType(Long.class);returnscript;}}

2. Lua脚本实现 (resources/scripts/trial_count.lua)

-- KEYS[1]: HyperLogLog的Key (e.g., "trial:hll:activity_1001")-- ARGV[1]: 用户唯一标识 (e.g., "user_12345")-- ARGV[2]: 活动总名额 (e.g., 1000)localhllKey=KEYS[1]localuserId=ARGV[1]localtotalQuota=tonumber(ARGV[2])-- 1. 先检查用户是否已存在 (PFCOUNT在单个元素上效率低,这里用PFADD的返回值判断)-- PFADD 返回 1 表示元素是新添加的,0 表示元素已存在localisAdded=redis.call('PFADD',hllKey,userId)-- 2. 获取当前估算的参与人数localcurrentCount=redis.call('PFCOUNT',hllKey)-- 3. 计算剩余名额localremaining=totalQuota-currentCountifremaining<0thenremaining=0end-- 4. 如果用户是新添加的,返回剩余名额;否则返回-1表示已参与ifisAdded==1thenreturnremainingelsereturn-1end

3. Java服务层调用

packagebaodanbao.com.cn.service;importbaodanbao.com.cn.exception.BusinessException;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.data.redis.core.script.DefaultRedisScript;importorg.springframework.stereotype.Service;importjava.util.Collections;/** * 霸王餐试吃服务 * @author baodanbao.com.cn */@ServicepublicclassTrialMealService{@AutowiredprivateStringRedisTemplateredisTemplate;@AutowiredprivateDefaultRedisScript<Long>trialCountScript;privatestaticfinalStringHLL_KEY_PREFIX="trial:hll:activity_";privatestaticfinallongTOTAL_QUOTA=1000L;// 活动总名额/** * 用户参与试吃并获取剩余名额 * @param activityId 活动ID * @param userId 用户ID * @return 剩余名额,-1表示已参与 */publiclongparticipateAndGetRemaining(StringactivityId,StringuserId){Stringkey=HLL_KEY_PREFIX+activityId;// 执行Lua脚本Longresult=redisTemplate.execute(trialCountScript,Collections.singletonList(key),userId,String.valueOf(TOTAL_QUOTA));if(result==null){thrownewBusinessException("系统繁忙,请稍后重试");}returnresult;}/** * 仅查询剩余名额(不增加用户) */publiclonggetRemainingQuota(StringactivityId){Stringkey=HLL_KEY_PREFIX+activityId;LongcurrentCount=redisTemplate.opsForHyperLogLog().size(key);longremaining=TOTAL_QUOTA-(currentCount==null?0:currentCount);returnMath.max(remaining,0);}}

4. 控制器层

packagebaodanbao.com.cn.controller;importbaodanbao.com.cn.service.TrialMealService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;/** * 试吃活动控制器 * @author baodanbao.com.cn */@RestController@RequestMapping("/api/trial")publicclassTrialMealController{@AutowiredprivateTrialMealServicetrialMealService;@PostMapping("/participate")publicResultparticipate(@RequestParamStringactivityId,@RequestParamStringuserId){longremaining=trialMealService.participateAndGetRemaining(activityId,userId);if(remaining==-1){returnResult.fail("您已参与过本次活动");}elseif(remaining>=0){returnResult.success("参与成功",remaining);}else{returnResult.fail("名额已抢光");}}@GetMapping("/remaining")publicResultgetRemaining(@RequestParamStringactivityId){longremaining=trialMealService.getRemainingQuota(activityId);returnResult.success("查询成功",remaining);}}
优势总结
  1. 内存效率:100万用户仅需12KB内存。
  2. 原子性:Lua脚本确保并发安全。
  3. 高性能:O(1)时间复杂度,支撑高并发。
  4. 实时性:毫秒级响应,用户体验流畅。

作为外卖霸王餐API唯一供给源头,俱美开放平台通过此方案确保了百万级并发下名额计数的准确与高效。

本文著作权归 俱美开放平台 ,转载请注明出处!

http://www.jsqmd.com/news/1230227/

相关文章:

  • Remount浏览器兼容性解决方案:从IE到现代浏览器的适配指南
  • 2026年7月最新天梭海口秀英万达广场维修保养服务电话 - 天梭服务中心
  • 挖到宝!论文全能网站合集,无限免费改稿 + 插图 + 查重降重,毕业通关必备
  • 鸿蒙 ArkTS 实战:Review Collector 从评价收集器到电商运营工具完整解析
  • 2026 徐州 7 月黄金回收行业全维度解析:从资质、检测、计价到售后,正规机构完整评判标准拆解 - 不晚生活号
  • 开源智能告警管理平台Keep:让运维从混乱走向清晰的实战指南
  • 2026年7月最新沈阳于洪区陵西街道亨得利官方名表服务中心电话公示 - 亨得利官方博客
  • 孕妇防妊娠纹用什么好?孕妈群的几个问题,一次说清楚 - 信息热点
  • 【201】进销存管理系统
  • 海浪波高预测(背景调研)
  • 有哪些不限次数免费修改、插图、查重降重的论文宝藏网站?
  • Capture.exe 设置中文后闪退
  • 【210】成绩管理系统
  • 肇庆电缆回收公司一体化回收:真实测享3家特色企业 - 广东再生资源回收
  • 【ROS】ros学习笔记(10)——通讯实操03_服务调用
  • 2026 礼品卡回收全攻略|闲置卡券安全变现,享卡回收实测避坑指南 - 信息热点
  • 2026年天津有毒气体报警器检测5家机构横向对比:资质、服务、费用全拆解 - 信息热点
  • 水晶轻奢跨境合规全手册:拆解品牌字体、天鹅剪影、水晶纹理三重商标完整保护边界
  • FreeRTOS学习笔记(九)
  • 2026年7月佛山工装与全案装修哪家强?头部合规企业推荐榜 - 互联网科技品牌测评
  • EasyX游戏:推箱子
  • 孕妇防妊娠纹用什么好?别瞎买!硬核拆解省下试错钱 - 信息热点
  • OData.NET版本兼容性指南:从OData v1-3到v4的迁移策略
  • RuoYi-Vue3代码生成器useDict白屏问题排查与修复
  • 01背包问题(二维动态规划法)的时间复杂度与空间复杂度
  • 软件工程毕业设计新颖的开题推荐
  • 深入理解 Go Context:从设计哲学到工程实践
  • 2026年自助洗车机靠谱公司推荐:选对品牌,创业成功的第一步 - Fan_00
  • EasyX游戏:太空射击
  • 基于Neh算法和禁忌搜索算法的排列流车间调度问题(PFSP)研究附Python代码