高并发抢票时,防止机器人刷票的令牌大闸,减轻服务器的压力(防刷+限流)

1. 为什么要引入令牌大闸?


场景1:分布式锁和限流都不能解决机器人刷票的问题,1000个请求抢票,900个限流快速失败,另外100个有可能是同一个在刷库。

引入令牌,令牌中记录用户信息,会进行校验用户是否拿过令牌,如果拿过令牌,那么几秒内不允许再获得令牌

场景2:没有余票时,需要查库存才能知道没票,会影响性能,不如查询令牌余票来的快

令牌的数量是和票数是相关的,令牌可以和票数相等,那么通过查询令牌就可以知道是否还有余票,会减少查询数据库,减少IO压力

2. 增加秒杀令牌表来维护令牌信息


增加一张表,表的创建SQL代码如下所示:

drop table if exists `sk_token`;  
create table `sk_token` (  `id` bigint not null comment 'id',  `date` date not null comment '日期',  `train_code` varchar(20) not null comment '车次编号',  `count` int not null comment '令牌余量',  `create_time` datetime(3) comment '新增时间',  `update_time` datetime(3) comment '修改时间',  primary key (`id`),  unique key `date_train_code_unique` (`date`, `train_code`)  
) engine=innodb default charset=utf8mb4 comment='秒杀令牌';

利用代码生成器生成相应的文件

3. 初始化车次信息时初始化令牌信息


在SkTokenService中实现genDaily方法

/**  * 初始化  */  
public void genDaily(Date date, String trainCode) {  LOG.info("删除日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);  SkTokenExample skTokenExample = new SkTokenExample();  skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);  skTokenMapper.deleteByExample(skTokenExample);  DateTime now = DateTime.now();  SkToken skToken = new SkToken();  skToken.setDate(date);  skToken.setTrainCode(trainCode);  skToken.setId(SnowUtil.getSnowflakeNextId());  skToken.setCreateTime(now);  skToken.setUpdateTime(now);  //计算该车次共有多少个座位int seatCount = dailyTrainSeatService.countSeat(date, trainCode);  LOG.info("车次【{}】座位数:{}", trainCode, seatCount);  //查询该车次共有多少个车站long stationCount = dailyTrainStationService.countByTrainCode(date, trainCode);  LOG.info("车次【{}】到站数:{}", trainCode, stationCount);  // 3/4需要根据实际卖票比例来定,一趟火车最多可以卖(seatCount * stationCount)张火车票  int count = (int) (seatCount * stationCount); // * 3/4);  LOG.info("车次【{}】初始生成令牌数:{}", trainCode, count);  skToken.setCount(count);  skTokenMapper.insert(skToken);  
}

然后在生成每日数据时加入该方法即可

//生成该车次的车站数据  
dailyTrainStationService.genDaily(date,train.getCode());  
//生成该车次的车厢数据  
dailyTrainCarriageService.genDaily(date,train.getCode());  
//生成该车次的座位数据  
dailyTrainSeatService.genDaily(date,train.getCode());  
//生成该车次的余票数据  
dailyTrainTicketService.genDaily(dailyTrain,date,train.getCode());  
LOG.info("生成日期【{}】车次【{}】的信息结束", DateUtil.formatDate(date), train.getCode());  
//生成令牌余量数据  
skTokenService.genDaily(date,train.getCode());

4. 增加校验秒杀令牌功能


在执行核心业务之前加上下面代码

//校验令牌容量  
boolean validSkToken=skTokenService.validSkToken(req.getDate(),req.getTrainCode(), req.getMemberId());  
if(validSkToken){  LOG.info("令牌校验通过");  
}else{  LOG.info("令牌校验不通过");  throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_SK_TOKEN_FAIL);  
}

其对应逻辑:先从redis缓存中查询令牌余量,如果存在缓存(60s过期),则直接从缓存中查询令牌余量,
如果余量大于0,则获取令牌,同时更新缓存中令牌余量
如果不存在缓存,则从数据库中查询

/**  * 校验令牌  */  
public boolean validSkToken(Date date, String trainCode, Long memberId) {  LOG.info("会员【{}】获取日期【{}】车次【{}】的令牌开始", memberId, DateUtil.formatDate(date), trainCode);  // 需要去掉这段,否则发布生产后,体验多人排队功能时,会因拿不到锁而返回:等待5秒,加入20人时,只有第1次循环能拿到锁  // if (!env.equals("dev")) {  //     // 先获取令牌锁,再校验令牌余量,防止机器人抢票,lockKey就是令牌,用来表示【谁能做什么】的一个凭证  //     String lockKey = RedisKeyPreEnum.SK_TOKEN + "-" + DateUtil.formatDate(date) + "-" + trainCode + "-" + memberId;  //     Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 5, TimeUnit.SECONDS);    //     if (Boolean.TRUE.equals(setIfAbsent)) {    //         LOG.info("恭喜,抢到令牌锁了!lockKey:{}", lockKey);  //     } else {    //         LOG.info("很遗憾,没抢到令牌锁!lockKey:{}", lockKey);  //         return false;    //     }    // }  String skTokenCountKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + DateUtil.formatDate(date) + "-" + trainCode;  Object skTokenCount = redisTemplate.opsForValue().get(skTokenCountKey);  if (skTokenCount != null) {  LOG.info("缓存中有该车次令牌大闸的key:{}", skTokenCountKey);  Long count = redisTemplate.opsForValue().decrement(skTokenCountKey, 1);  if (count < 0L) {  LOG.error("获取令牌失败:{}", skTokenCountKey);  return false;  } else {  LOG.info("获取令牌后,令牌余数:{}", count);  redisTemplate.expire(skTokenCountKey, 60, TimeUnit.SECONDS);  // 每获取5个令牌更新一次数据库  if (count % 5 == 0) {  skTokenMapperCust.decrease(date, trainCode, 5);  }  return true;  }  } else {  LOG.info("缓存中没有该车次令牌大闸的key:{}", skTokenCountKey);  // 检查是否还有令牌  SkTokenExample skTokenExample = new SkTokenExample();  skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);  List<SkToken> tokenCountList = skTokenMapper.selectByExample(skTokenExample);  if (CollUtil.isEmpty(tokenCountList)) {  LOG.info("找不到日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);  return false;  }  SkToken skToken = tokenCountList.get(0);  if (skToken.getCount() <= 0) {  LOG.info("日期【{}】车次【{}】的令牌余量为0", DateUtil.formatDate(date), trainCode);  return false;  }  // 令牌还有余量  // 令牌余数-1  Integer count = skToken.getCount() - 1;  skToken.setCount(count);  LOG.info("将该车次令牌大闸放入缓存中,key: {}, count: {}", skTokenCountKey, count);  // 不需要更新数据库,只要放缓存即可  redisTemplate.opsForValue().set(skTokenCountKey, String.valueOf(count), 60, TimeUnit.SECONDS);  skTokenMapper.updateByPrimaryKey(skToken);  return true;  }  // 令牌约等于库存,令牌没有了,就不再卖票,不需要再进入购票主流程去判断库存,判断令牌肯定比判断库存效率高  // int updateCount = skTokenMapperCust.decrease(date, trainCode, 1);  // if (updateCount > 0) {    //     return true;    // } else {    //     return false;    // }}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765890.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于ssm的农家乐管理系统+数据库+论文+免费远程调试

开发环境 项目编号:JavaMySQL ssm231农家乐管理系统-民宿-餐饮-房间预定-vue 开发语言&#xff1a;Java 开发工具:IDEA /Eclipse 数据库:MYSQL5.7 应用服务:Tomcat7/Tomcat8 使用框架:ssmvue 项目介绍: ssm的农家乐管理系统。Javaee项目。采用M&#xff08;model&#xff09;V…

[思考记录]技术欠账

最近对某开发项目做回顾梳理&#xff0c;除了进一步思考整理相关概念和问题外&#xff0c;一个重要的任务就是清理“技术欠账”。 这个“技术欠账”是指在这个项目的初期&#xff0c;会有意无意偏向快速实现&#xff0c;想先做出来、用起来&#xff0c;进而在实现过程中做出…

jenkins构建完成后部署到本机,无法读取容器外文件夹

项目背景&#xff1a; Dockerjenkins 构建完成后&#xff0c;要把打包的dist文件夹内容移动到网站目录 /www/wwwroot/xxxxxx 文件夹下&#xff1b;但是获取不到jenkins容器外的文件夹。 解决办法&#xff1a; 在容器中&#xff0c;添加挂载/映射本机目录&#xff0c;把网站…

C++简单实现哈希查找

C 简单实现哈希查找 1. 哈希冲突 哈希表中可能会出现哈希冲突&#xff0c;即多个数据项映射到相同的桶。 常见的冲突解决方法包括链地址法&#xff08;Chaining&#xff09;和线性探测法&#xff08;Linear Probing&#xff09;。 使用链地址法时&#xff0c;每个桶维护一个链…

Oracle里表、索引、列的统计信息

目录 一、表的统计信息 二、索引的统计信息 1、层级&#xff08;level&#xff09; 2、聚簇因子的含义及重要性 3、列的统计信息 3.1直方图&#xff08;histogram&#xff09; 1&#xff09;直方图含义 2&#xff09;直方图类型 一、表的统计信息 表的统计信息用于描述表…

【保姆级教程】YOLOv8目标检测:训练自己的数据集

一、YOLOV8环境准备 1.1 下载安装最新的YOLOv8代码 仓库地址&#xff1a; https://github.com/ultralytics/ultralytics1.2 配置环境 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple二、数据准备 2.1 安装labelme标注软件 pip install label…

React系列 之 React进阶 含源码解读 (一)事件合成、state原理

资料来源&#xff1a;掘金课程 https://juejin.cn/book/6945998773818490884?enter_fromcourse_center&utm_sourcecourse_center 记录一些笔记 事件合成 React的事件其实是React重新实现的一套事件系统。目标是统一管理事件&#xff0c;提供一种跨浏览器一致性的事件处…

langchain4j DefaultAiServices源码解析

版本 0.28.0 源码 使用langchain4j&#xff0c;可以通过AiServices来封装聊天模型API&#xff0c;实现会话记忆&#xff0c;工具调用&#xff0c;搜索增强&#xff0c;内容审查等功能&#xff0c;并提供简单灵活的用户接口 DefaultAiServices是其默认实现类型&#xff0c;通…

5G智能网关助力工业铸造设备监测升级

随着物联网技术的迅猛发展和工业4.0浪潮的推进&#xff0c;传统工业正面临着严峻的转型升级压力。在这一背景下&#xff0c;铸造行业——这一典型的传统重工业领域&#xff0c;也必须积极探索借助5G、物联网、边缘计算等技术提升生产经营效率的新路径。 本文就基于佰马合作伙伴…

【技巧】ChatGPT Prompt 提示语大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 主要来自&#xff1a;https://github.com/f/awesome-chatgpt-prompts ChatGPT SEO提示 Contributed by: StoryChief AI Reference: 7 Powerful ChatGPT Prompts to Create SEO Content Faster 供稿人&#xff1a;…

MyBatis Plus笔记

1、删除 物理删除&#xff1a;从硬盘上直接删除掉 。好处&#xff1a;数据条数少了&#xff0c;不好的地方在于可能会影响到基于这条数据产生的记录 逻辑删除&#xff1a;假删除 两个区别&#xff1a; 删除时 之前&#xff1a;DELETE from sys_user …

链表oj测试题(上)

链表的申明&#xff1a; struct ListNode {int val;struct ListNode* next; }; 1.题1 删除指定元素 例如&#xff1a;链表1 2 6 3 4 5 6&#xff0c;然后选择删除元素6&#xff0c;返回的链表为1 2 3 4 5 。 代码演示&#xff1a; typedef struct ListNode ListNode;List…

Spark与flink计算引擎工作原理

Spark是大批量分布式计算引擎框架&#xff0c;scale语言开发的&#xff0c;核心技术是弹性分布式数据集&#xff08;RDD&#xff09;可以快速在内存中对数据集进行多次迭代&#xff0c;支持复杂的数据挖掘算法及图形计算算法&#xff0c;spark与Hadoop区别主要是spark多个作业之…

什么是行业垂直类媒体?有哪些?怎么邀约

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体胡老师。 行业垂直类媒体是聚焦于特定行业或领域的媒体平台。 行业垂直类媒体不同于主流媒体&#xff0c;它们专注于提供与某个特定领域相关的深入内容和服务&#xff0c;例如商业新闻、旅游、数字…

能拍英语的搜题软件?九个免费好用的大学生搜题工具 #经验分享#知识分享#其他

积极参加社团活动和实践项目&#xff0c;可以帮助大学生拓宽人脉圈和锻炼实际操作能力。 1.粉鹿搜题 这是一个公众号 搜题拥有非常强大的题库&#xff0c;包含IT认证、建筑工程:、会计资格、教师资格、研究生、公务员等类型的题目。 下方附上一些测试的试题及答案 1、BPR基…

面试算法-79-搜索旋转排序数组

题目 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nums[1…

Qt 利用共享内存实现一次只能启动一个程序(单实例运行)

Qt 利用共享内存实现一次只能启动一个程序 文章目录 Qt 利用共享内存实现一次只能启动一个程序摘要利用共享内存实现一次只能启动一个程序示例代码 关键字&#xff1a; Qt、 unique、 单一、 QSharedMemory、 共享内存 摘要 今天接着在公司搞我的屎山代码&#xff0c;按照…

[MAUI]集成高德地图组件至.NET MAUI Blazor项目

文章目录 前期准备&#xff1a;注册高德开发者并创建 key登录控制台创建 key获取 key 和密钥 创建项目创建JS API Loader配置权限创建定义创建模型创建地图组件创建交互逻辑 项目地址 地图组件在手机App中常用地理相关业务&#xff0c;如查看线下门店&#xff0c;设置导航&…

LeetCode 热题 100 | 堆(二)

目录 1 什么是优先队列 1.1 优先队列与堆的关系 1.2 如何定义优先队列 1.3 如何使用优先队列 1.4 如何设置排序规则 2 347. 前 K 个高频元素 2.1 第 2 步的具体实现 2.2 举例说明 2.3 完整代码 3 215. 数组中的第 K 个最大元素 - v2 菜鸟做题&#xff0c;语…

Shell学习

一、 变量 shell是弱类型语言&#xff0c;不用定义数据类型&#xff0c;默认都是字符串。 变量与值之间不得有空格 只能包含数字、字母、下划线 不能以数字开头 区分大小写 根据变量的作用域可以将shell变量分为&#xff1a;全局变量、局部变量、环境变量。全局变量通常和…