dp秒杀优惠券

1、全局id生成器

当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题:

  • id的规律性太明显
  • 受单表数据量的限制

场景分析:如果我们的id具有太明显的规则,用户或者说商业对手很容易猜测出来我们的一些敏感信息,比如商城在一天时间内,卖出了多少单,这明显不合适。

场景分析二:随着我们商城规模越来越大,mysql的单表的容量不宜超过500W,数据量过大之后,我们要进行拆库拆表,但拆分表了之后,他们从逻辑上讲他们是同一张表,所以他们的id是不能一样的, 于是乎我们需要保证id的唯一性

@Component
public class RedisIdWorker {/*** 开始时间戳*/private static final long BEGIN_TIMESTAMP = 1640995200L;/*** 序列号的位数*/private static final int COUNT_BITS = 32;private StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public long nextId(String keyPrefix) {// 1.生成时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);// 2.生成序列号long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2.1.获取当前日期,精确到天// 2.2.自增长String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回return timestamp << COUNT_BITS | count;}}

利用线程池创建300个并发线程,每个线程生成100个id,总耗时time = 2629ms

@Test
void testIdWorker() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(300);Runnable task = () -> {for (int i = 0; i < 100; i++) {long id = redisIdWorker.nextId("order");System.out.println("id = " + id);}latch.countDown();
};
long begin = System.currentTimeMillis();
for (int i = 0; i < 300; i++) {es.submit(task);
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("time = " + (end - begin));
}

2、添加优惠券,实现秒杀下单

下单时需要判断两点:

  • 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单
  • 库存是否充足,不足则无法下单

@Service
@Transactional
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate SeckillVoucherServiceImpl seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Overridepublic Result seckillVoucher(Long voucherId) {// 1、查询优惠券信息SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2、判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now()) || voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail("秒杀未开始");}// 3、判断库存是否充足if(voucher.getStock()<1) {return Result.fail("库存不足");}// 4、扣减库存voucher.setStock(voucher.getStock()-1);boolean success = seckillVoucherService.updateById(voucher);if(!success) {return Result.fail("库存不足");}// 5、创建订单VoucherOrder voucherOrder = new VoucherOrder();// 5.1.订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 5.2.用户idLong userId = UserHolder.getUser().getId();voucherOrder.setUserId(userId);// 5.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);return Result.ok(orderId);}
}

3、解决超卖问题

jmeter分析时记得在HTTP信息头管理器中加上token

测试1秒200个并发量,发现会出现超卖问题,100个订单扣减,库存剩下-9个。

乐观锁解决超卖问题

// 4、扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock= stock -1")
.eq("voucher_id", voucherId)
.gt("stock", 0)
.update();

4、一人一单

需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单

// 根据用户id与优惠券id,判断订单是否存在
Long useId = UserHolder.getUser().getId();
Integer count = query().eq("user_id", useId).eq("voucher_id", voucherId).count();
if(count > 0) {return Result.fail("用户已经购买过一次!");
}
// 4、扣减库存boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).gt("stock", 0).update();

但是上述代码涉及到查询与修改,因此还是会有多线程安全问题。用jmeter测试,发现还是有相同用户id与优惠券id的订单超卖,没有达到一人一单的需求。

因此尝试加锁。由于存在较多的写操作,因此采用悲观锁。但如果对后面一大段设计增删改查的代码加锁,锁粒度太大。如下代码所示。

@Transactional
public synchronized Result createVoucherOrder(Long voucherId) {Long userId = UserHolder.getUser().getId();// 5.1.查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();// 5.2.判断是否存在if (count > 0) {// 用户已经购买过了return Result.fail("用户已经购买过一次!");}// 6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") // set stock = stock - 1.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0.update();if (!success) {// 扣减失败return Result.fail("库存不足!");}// 7.创建订单VoucherOrder voucherOrder = new VoucherOrder();// 7.1.订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 7.2.用户idvoucherOrder.setUserId(userId);// 7.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7.返回订单idreturn Result.ok(orderId);
}

存在两个问题:

1)且由于在createVoucherOrder代码外加上事务,事务包含锁,因此会导致加锁读操作后事务还未提交,就提前将所释放,下一个线程获取锁时,读取到的数据库的值为旧值,造成数据不一致性,因此需要在事务外加锁。

2)将synchronized加在方法上,相当于是对this加锁,因此多线程过来加的是一把锁,串行化,性能差。由于需求是一人一单,因此只需要对同一用户加锁。因此去除ThreadLocal中的userId进行加锁。但每次线程进入createVoucherOrder方法都会新建一个userId对象,所以其实本质上还是对不同的对象进行了加锁,userId.toString()的底层也是new一个string对象,但我们需要的是同一用户只有一把锁,因此需要intern() 这个方法从常量池中拿到数据。修改代码:

@Overridepublic Result seckillVoucher(Long voucherId) {// 1、查询优惠券信息SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2、判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now()) || voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail("秒杀未开始");}// 3、判断库存是否充足Integer stock = voucher.getStock();if(voucher.getStock()<1) {return Result.fail("库存不足");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {return createVoucherOrder(voucherId);}}@Transactionalpublic Result createVoucherOrder(Long voucherId) {Long userId = UserHolder.getUser().getId();// 根据用户id与优惠券id,判断订单是否存在int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if(count > 0) {return Result.fail("用户已经购买过一次!");}// 4、扣减库存boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {//扣减库存return Result.fail("库存不足!");}// 5、创建订单VoucherOrder voucherOrder = new VoucherOrder();// 5.1.订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 5.2.用户idvoucherOrder.setUserId(userId);// 5.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);return Result.ok(orderId);}

但是以上做法依然有问题,因为你调用的方法,其实是this.的方式调用的,事务想要生效,还得利用代理来生效,所以这个地方,我们需要获得原始的事务对象, 来操作事务

1)在启动类上加上

2)在pom.xml文件里加上依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId>
</dependency>

3)修改代码

synchronized (userId.toString().intern()) {IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);
}

记得将seckillVoucher方法上的事务注解取消,否则还是会出现上述问题。

查看数据库,成功实现一人一单

5、集群环境下的并发问题

通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了。

开启两份服务,同一用户下单两次(负载均衡算法采用轮询),库存扣减两次。

有关锁失效原因分析

由于现在我们部署了多个tomcat,每个tomcat都有一个属于自己的jvm,那么假设在服务器A的tomcat内部,有两个线程,这两个线程由于使用的是同一份代码,那么他们的锁对象是同一个,是可以实现互斥的,但是如果现在是服务器B的tomcat内部,又有两个线程,但是他们的锁对象写的虽然和服务器A一样,但是锁对象却不是同一个,所以线程3和线程4可以实现互斥,但是却无法和线程1和线程2实现互斥,这就是 集群环境下,syn锁失效的原因,在这种情况下,我们就需要使用分布式锁来解决这个问题。

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

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

相关文章

前端实时更新数据的几种方式

实时更新数据的几种方式 背景 在我们的日常工作中,我们往往会遇到客户端需要实时获取服务端最新数据的场景,例如聊天系统(WeChat/Telegram),股票行情查看软件(同花顺/富途),feed 推送系统(Twitter/微博)等等。在实现这些需求的时候,我们的技术方案是有很多的,本文将会给…

C++修改文件后缀名;链表循环删除乘积为10的元素

1. 文件名修改 在一个文件目录下&#xff0c;存在相同扩展名 ".stp"的多个文件&#xff0c;对这样的文件名&#xff0c;请修改文件名称&#xff0c;在文件 名称后增加排序标识 "-01" &#xff0c; "-02" &#xff0c; "-03"... #incl…

python基于百度,哈工大等停用表进行的中文分词

import os import pandas as pd import jieba# 加载停用词 def load_stopwords(filenames):stopwords set()for filename in filenames:with open(filename, r, encodingutf-8) as f:for line in f:stopwords.add(line.strip())return stopwords# 中文分词并去除停用词 def se…

企业网站有必要进行软件测试吗?网站测试有哪些测试流程?

企业网站在现代商业中扮演着重要的角色&#xff0c;它不仅是企业形象的重要体现&#xff0c;也是与客户、合作伙伴进行沟通与交流的重要渠道。然而&#xff0c;由于企业网站的复杂性和关键性&#xff0c;其中可能存在各种潜在的问题和隐患。因此&#xff0c;对企业网站进行软件…

企业如何安全的使用U盘

问题的背景&#xff1a; U盘&#xff08;USB闪存盘&#xff09;的优点主要包括&#xff1a; 便携性&#xff1a;U盘体积小、重量轻&#xff0c;便于携带&#xff0c;可以轻松地在不同设备间传输数据。高速传输&#xff1a;相比传统机械硬盘&#xff0c;U盘的读写速度更快&…

el-upload上传文件使用http-request方法,formdata传集合List到后台

el-upload上传文件 前言1、使用el-upload上传文件1.1代码演示1.2回显列表2、formdata传集合List到Springboot后台前言 在使用el-upload上传文件,会遇到必须使用:action="upload_url"远端链接的问题,本章我们讲解怎样不适用远端链接,通过上传获取到本地的file文件…

海尔智家牵手罗兰-加洛斯,看全球创牌再升级

晚春的巴黎西郊&#xff0c;古典建筑群与七叶树林荫交相掩映&#xff0c;坐落于此的罗兰加洛斯球场内座无虚席。 来自全球各地的数万观众&#xff0c;正与场外街道上的驻足者们一起&#xff0c;等待着全世界最美好的网球声响起…… 当地时间5月26日&#xff0c;全球四大职业网…

RFM模型-分析母婴类产品

1&#xff0c;场景描述 假设我们是某电商平台的数据分析师&#xff0c;负责分析母婴产品线的用户数据。母婴产品的购买行为具有一定的周期性和生命周期特征&#xff0c;如用户在不同怀孕阶段的需求不同&#xff0c;以及宝宝出生后的不同成长阶段需要不同的产品。 2&#xff0…

Flutter 中的 RichText 小部件:全面指南

Flutter 中的 RichText 小部件&#xff1a;全面指南 Flutter 是一个流行的跨平台 UI 工具包&#xff0c;它允许开发者使用 Dart 语言来构建高性能、高保真的移动应用。在 Flutter 中&#xff0c;RichText 是一个非常有用的小部件&#xff0c;它允许开发者在同一个文本行中混合…

XV7011BB可为智能割草机的导航系统提供新的解决方案

智能割草机作为现代家庭和商业草坪维护保养的重要工具&#xff0c;其精确的定位和导航系统对于提高机器工作效率和确保安全运行至关重要。在智能割草机的发展历程中&#xff0c;定位和导航技术一直是关键的创新点。 传统的基于RTK(实时动态差分定位技术)技术的割草机虽然在…

景源畅信电商:抖音开店步骤是什么?

随着社交媒体的兴起&#xff0c;抖音已经成为一个不可忽视的电商平台。许多人都希望通过抖音开店来实现自己的创业梦想。那么&#xff0c;抖音开店的具体步骤是什么呢?接下来&#xff0c;我们将详细阐述这一问题。 一、明确回答问题抖音开店的步骤主要包括&#xff1a;注册账号…

1初识C#

1、Console安慰 Console.WriteLine("Hello, world!"); // 输出 "Hello, world!" 并换行 Console.WriteLine(123.45); // 输出数字 123.45 并换行 Console.WriteLine("Name: " name); // 输出 "Name: [变量name的值]" 并换行 2、 C…

Vue 3 教程:核心知识

Vue 3 教程&#xff1a;核心知识 1. Vue3简介1.1. 【性能的提升】1.2.【 源码的升级】1.3. 【拥抱TypeScript】1.4. 【新的特性】 2. 创建Vue3工程2.1. 【基于 vue-cli 创建】2.2. 【基于 vite 创建】(推荐)2.3. 【一个简单的效果】 3. Vue3核心语法3.1. 【OptionsAPI 与 Compo…

贪心算法教程(个人总结版)

背景 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每一步选择中都采取在当前状态下最好或最优的选择&#xff0c;期望通过局部最优选择达到全局最优解决方案的算法。贪心算法的应用广泛&#xff0c;包括图算法、动态规划、贪心选择、装载问题等。它通常用于解决…

【C++】---二叉搜索树

【C】---二叉搜索树 一、二叉搜索树概念二、二叉搜索树操作&#xff08;非递归&#xff09;1.二叉搜索树的查找 &#xff08;非递归&#xff09;&#xff08;1&#xff09;查找&#xff08;2&#xff09;中序遍历 2.二叉搜索树的插入&#xff08;非递归&#xff09;3.二叉搜索树…

Java 实现二叉搜索树 代码

新建文件 创建TreeNode类&#xff0c;实例化 直接在BinarySearchTree类里面写就可以 static class TreeNode {public int key;public TreeNode left;public TreeNode right;TreeNode(int key) {this.key key;}}public TreeNode root; 插入节点 insert public boolean inser…

Spring创建对象的多种方式

一、对象分类 简单对象&#xff1a;使用new Obj()方式创建的对象 复杂对象&#xff1a;无法使用new Obj()方式创建的对象。例如&#xff1a; 1. AOP创建代理对象。ProxyFactoryBean; 2. Mybatis中的SqlSessionFactoryBean; 3. Hibernate中的SessionFactoryBean。二、创建对象方…

创新案例 | 持续增长,好孩子集团的全球化品牌矩阵战略与客户中心设计哲学

探索好孩子集团如何通过创新设计的全球化品牌矩阵和以客户为中心的产品策略&#xff0c;在竞争激烈的母婴市场中实现持续增长。深入了解其品牌价值观、市场定位策略以及如何满足新一代父母的需求。本文旨在为中高级职场人士、创业家及创新精英提供深度见解&#xff0c;帮助他们…

最新上市公司控制变量大全(1413+指标)1990-2023年

数据介绍&#xff1a;根据2023年上市公司年报数据进行更新&#xff0c;包括基本信息、财务指标、环境、社会与治理、数字化转型、企业发展、全要素生产率等1413指标。数据范围&#xff1a;A股上市公司数据年份&#xff1a;1990-2023年指标数目&#xff1a;1413个指标&#xff0…

在云中确保安全的五个技巧

随着采用云计算战略并开始充分意识到云计算技术可以提供的回报&#xff0c;企业可以做些什么来改善他们的风险状况?以下是德迅云安全在云中确保安全的五个技巧。 德迅云安全对如何在云计算基础设施中确保安全的五个技巧进行了阐述和分析。 在当今的混合工作环境中&#xff0c…