热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

黑马点评优惠卷秒杀

黑马点评–优惠卷秒杀全局ID生成器:是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:为了增加ID的安全性ÿ




黑马点评–优惠卷秒杀

全局ID生成器:

是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvc2qD5J-1668574944843)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221113221848596.png)]

为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FM7N8l3N-1668574944844)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221113222812554.png)]

Redis自增ID策略:


  • 每天一个key,方便统计订单量
  • iD结构是时间戳+计数器

/**
* 开始时间戳
*/

private static final long BEGIN_TIMESTAMP = 1640995200;
/**
* 序列号的位数
*/

private static final int COUNT_BITS = 32;
/**
* id生成策略
*
* @param keyPrefix 业务前缀
* @return
*/

@Resource
private StringRedisTemplate stringRedisTemplate;
public long nextId(String keyPrefix) {
//1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
//2.生成序列号
//2.1获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
//2.2自增长
Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
//3.拼接并返回
return timestamp << COUNT_BITS | count;
}
public static void main(String[] args) {
LocalDateTime time &#61; LocalDateTime.of(2022, 1, 1, 0, 0, 0);
long second &#61; time.toEpochSecond(ZoneOffset.UTC);
System.out.println(second);
}

测试ID自增生成策略&#xff1a;

500个线程每一个线程生成100id一共50000个id花的时间&#xff1a;

private ExecutorService service &#61; Executors.newFixedThreadPool(500);
&#64;Test
void testIdWorker() throws InterruptedException {
CountDownLatch latch &#61;new CountDownLatch(500);
Runnable task &#61; ()->{
for (int i&#61;0;i<100;i&#43;&#43;){
long id&#61; redisIdWorker.nextId("order");
System.out.println("id&#61;" &#43;id);
}
latch.countDown();
};
long start&#61;System.currentTimeMillis();
for (int i&#61;0;i<500;i&#43;&#43;){
service.submit(task);
}
latch.await();
long end&#61;System.currentTimeMillis();
System.out.println("end&#61;" &#43;(end-start));
}

每个店铺都可以发布优惠卷&#xff1a;

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


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

全局唯一ID生成策略&#xff1a;


  • UUID
  • Redis自增
  • sonwflake算法
  • 数据库自增

实现优惠券秒杀下单

每个店铺都可以发布优惠券&#xff0c;分为平价卷和特价卷。平价劵可以任意购买&#xff0c;而特价劵需要秒杀抢购&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLOQ08x3-1668574944845)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114160638726.png)]

表关系如下&#xff1a;


  • tb_voucher:优惠劵的基本信息&#xff0c;优惠金额&#xff0c;使用规则等
  • tb_seckill_voucher:优惠劵的库存&#xff0c;开始抢购时间&#xff0c;结束抢购时间。特价优惠券才需要填写这些信息

在VoucherController中提供一个接口&#xff0c;可以添加秒杀优惠券&#xff1a;http://localhost:8081/voucher/seckill

{
"shopId":1,
"title":"100元代金券",
"subTitle":"周一至周五均可使用",
"rules": "全场通用\\n无需预约\\n可无限叠加\\不兑换、不找零\\n仅限堂食",
"payValue": 8000,
"actualValue": 10000,
"type": 1,
"stock": 100,
"beginTime": "2022-10-25T12:09:04",
"endTime": "2022-12-25T12:09:04"
}

可以通过postman调用

实现秒杀下单&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v54T0KOV-1668574944846)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114192754339.png)]

下单时需要判断两点&#xff1a;


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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1AtDzqAS-1668574944846)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114193239439.png)]

库存超卖问题分析&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMPRwTiA-1668574944847)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114233014147.png)]

超卖问题是典型的多线程安全问题&#xff0c;针对这一问题的常见解决方案就是加锁&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iKY8HQuU-1668574944848)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114233544182.png)]

乐观锁&#xff1a;


  • 版本号法
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBV8SEkv-1668574944849)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114234218883.png)]

  • CAS法&#xff08;CompareAndSet&#xff09;


    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4q4SdHyN-1668574944849)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221114234628336.png)]

乐观锁解决超卖问题&#xff1a;

//1.查询优惠劵
SeckillVoucher voucher &#61; iSeckillVoucherService.getById(voucherId);
//2.判断秒杀是否开始
LocalDateTime beginTime &#61; voucher.getBeginTime();
if (beginTime.isAfter(LocalDateTime.now())) {
//尚未开始
return Result.fail("活动尚未开始");
}
//3.判断秒杀是否已经结束
LocalDateTime endTime &#61; voucher.getEndTime();
if (LocalDateTime.now().isAfter(endTime)) {
//已结束
return Result.fail("活动已经结束");
}
//4判断库存是否充足
if (voucher.getStock() < 1) {
//库存不足
return Result.fail("库存不足!");
}
//5.扣减库存
boolean success &#61;iSeckillVoucherService.update()
.setSql("stock &#61;stock -1")
.eq("voucher_id",voucherId).gt("stock",0).
update();
if (!success) {
//扣减失败
return Result.fail("库存不足!");
}
//6.创建订单
VoucherOrder voucherOrder &#61; new VoucherOrder();
//6.1 订单id
long orderId &#61; redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//6.2 用户id
Long userId &#61; UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
//6.3 代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 7.返回订单id
return Result.ok(orderId);

超卖这样的线程安全问题&#xff0c;解决方案有哪些&#xff1f;

1.悲观锁&#xff1a;添加同步锁&#xff0c;让线程串行执行


  • 优点&#xff1a;简单粗暴
  • 缺点&#xff1a;性能一般

2.乐观锁&#xff1a;不加锁&#xff0c;在更新时判读是否有其它线程在修改

一人一单&#xff1a;

需求&#xff1a;修改秒杀业务&#xff0c;要求同一个优惠券&#xff0c;一个用户只能下一单

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDASAcEo-1668575438357)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221116130925403.png)]

&#64;Autowired
private ISeckillVoucherService iSeckillVoucherService;
&#64;Autowired
private RedisIdWorker redisIdWorker;
&#64;Override
public Result seckillVoucher(Long voucherId) {
//1.查询优惠劵
SeckillVoucher voucher &#61; iSeckillVoucherService.getById(voucherId);
//2.判断秒杀是否开始
LocalDateTime beginTime &#61; voucher.getBeginTime();
if (beginTime.isAfter(LocalDateTime.now())) {
//尚未开始
return Result.fail("活动尚未开始");
}
//3.判断秒杀是否已经结束
LocalDateTime endTime &#61; voucher.getEndTime();
if (LocalDateTime.now().isAfter(endTime)) {
//已结束
return Result.fail("活动已经结束");
}
//4判断库存是否充足
if (voucher.getStock() < 1) {
//库存不足
return Result.fail("库存不足!");
}
Long userId &#61; UserHolder.getUser().getId();
synchronized (userId.toString().intern()){
//获取spring事务代理对象
IVoucherOrderService proxy &#61; (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
}
&#64;Transactional
public Result createVoucherOrder(Long voucherId) {
//6.一个人一单
Long userId &#61; UserHolder.getUser().getId();
//6.1查询订单
int count &#61; query().eq("user_id", userId).eq("voucher_id", voucherId).count();
//6.2判断是否存在
if (count > 0) {
//用户以及购买过
return Result.fail("用户已经购买过一次");
}
//7.扣减库存
boolean success &#61; iSeckillVoucherService.update()
.setSql("stock &#61;stock -1")
.eq("voucher_id", voucherId)
.gt("stock", 0).update();
if (!success) {
//扣减失败
return Result.fail("库存不足!");
}
//8.创建订单
VoucherOrder voucherOrder &#61; new VoucherOrder();
//8.1 订单id
long orderId &#61; redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//8.2 用户id
voucherOrder.setUserId(userId);
//8.3 代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 9.返回订单id
return Result.ok(orderId);
}

一人一单的并发安全问题&#xff1a;

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

1.我们将服务启动两份&#xff0c;端口分别为8081和8082&#xff1a;

在这里插入图片描述

2.修改nginx的conf目录下的nginx.conf文件&#xff0c;配置反向代理和负载均衡&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bF5Dvek3-1668574944852)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221116123310064.png)]

现在&#xff0c;用户请求会在这两个节点上负载均衡&#xff0c;再次测试下是否存在线程安全问题。

一人一单的并发安全问题&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Ikza5ar-1668574944856)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221116125039581.png)]







推荐阅读
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了如何将CIM_DateTime解析为.Net DateTime,并分享了解析过程中可能遇到的问题和解决方法。通过使用DateTime.ParseExact方法和适当的格式字符串,可以成功解析CIM_DateTime字符串。同时还提供了关于WMI和字符串格式的相关信息。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
author-avatar
bj韩式尕伙
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有