【黑马点评Redis——003优惠券秒杀】

1.优惠券秒杀

1.1 全局ID生成器

1.1.1 什么是全局ID生成器

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具

需要满足以下特性:

  • 唯一性
  • 高可用
  • 高性能
  • 递增性
  • 安全性

1.1.2 为什么需要全局ID生成器?

自增ID存在的问题:

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

1.1.2 如何构建一个全局ID生成器

全局唯一ID生成策略

  • UUID
  • Redis自增(可以携带一些信息)
  • snowflake算法
  • 数据库自增
    Redis自增ID策略
  • 每天一个key,方便统计订单量
  • ID构造是时间搓+计数器
    在这里插入图片描述
    ID的组成部分:
  • 符号位:1bit,永远为0
  • 时间戳:31bit,以秒为单位,可以使用69年
  • 序列号:32bit,秒内的计数器,支持每秒最多可以产生2^32个不同ID

1.2 优惠券秒杀的下单功能流程图

在这里插入图片描述

1.3 库存超卖问题

  • 悲观锁:添加同步锁,让线程串行执行
    • 优点:简单粗暴
    • 缺点:性能一般
  • 乐观锁:不加锁,在更新时判断是否有其它线程在修改
    • 优点:性能好
    • 缺点:存在成功率低的问题
      在这里插入图片描述

1.4 乐观锁解决超卖

乐观锁的关键是判断之前的数据是否有被修改过,常见的方式有两种:

  • 版本号法(在这里库存可以当做版本号)
  • CAS法

在这里插入图片描述

    @Transactional@Overridepublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){// 尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否已经结束if(voucher.getEndTime().isBefore(LocalDateTime.now())){// 尚未开始return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if(voucher.getStock() < 1){return Result.fail("库存不足");}// 5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock",0).update();if (!success){return Result.fail("库存不足");}// 6.创建订单VoucherOrder voucherOrder = new VoucherOrder();// 6.1 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 6.2 用户idLong userId = UserHolder.getUser().getId();voucherOrder.setUserId(userId);// 6.3 代金券idvoucherOrder.setVoucherId(voucherId);// 7.返回订单idreturn Result.ok(orderId);}

1.5 实现一人一单

在这段代码中我们需要先判断该用户是否已经购买过优惠券,我们需要对用户Id进行加锁,通过userId.toString().intern()来获取同一个对象。同时通过代理来防止事务失效。

   @Overridepublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){// 尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否已经结束if(voucher.getEndTime().isBefore(LocalDateTime.now())){// 尚未开始return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if(voucher.getStock() < 1){return Result.fail("库存不足");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()){// 获取代理对象(事务),通过代理对象防止Transaction失效IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactionalpublic Result createVoucherOrder(Long voucherId){// 5.一人一单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").eq("voucher_id", voucherId).gt("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);// 8. 返回订单idreturn Result.ok(orderId);}

2. 知识储备

2.1 事务失效的常见原因

2.1.1 访问权限问题

众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。

但如果我们在开发过程中,把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题。
spring要求被代理方法(开启事务的方法)必须是public的。

也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是public,而是private、default或protected的话,spring则不会提供事务功能。

2.1.2 方法用final修饰

有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,这样会导致事务失效。
如果你看过spring事务的源码,可能会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。

 注意:如果某个方法是static的,同样无法通过动态代理,变成事务方法。

2.1.3 方法内部调用

在某个Service类的某个方法中,调用另外一个事务方法。

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic void add(UserModel userModel) {userMapper.insertUser(userModel);updateStatus(userModel);}@Transactionalpublic void updateStatus(UserModel userModel) {doSameThing();}
}

我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

由此可见,在同一个类中的方法直接内部调用,会导致事务失效。

如何解决这个问题

2.1.3.1 新加一个Service方法

只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下:

@Servcie
public class ServiceA {@Autowiredprvate ServiceB serviceB;public void save(User user) {queryData1();queryData2();serviceB.doSave(user);}}@Servciepublic class ServiceB {@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}
2.1.3.2 在该Service类中注入自己

如果不想再新加一个Service类,在该Service类中注入自己也是一种选择。具体代码如下:

@Servcie
public class ServiceA {@Autowiredprvate ServiceA serviceA;public void save(User user) {queryData1();queryData2();serviceA.doSave(user);}@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}
2.1.3.3 通过AopContent类

可以通过在该Service类中使用AOPProxy获取代理对象,实现相同的功能。

@Servcie
public class ServiceA {public void save(User user) {queryData1();queryData2();((ServiceA)AopContext.currentProxy()).doSave(user);}@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}

2.1.4 未被spring管理

在我们平时开发过程中,有个细节很容易被忽略。即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。

通常情况下,我们通过@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。

2.1.5 多线程调用

spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。
我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

2.1.6 表不支持事务

在mysql5之前,默认的数据库引擎是myisam。它的好处就不用多说了:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比innodb更好。myisam好用,但有个很致命的问题是:不支持事务。

2.1.7 未开启事务

2.2 toString().intern()的作用

intern() 方法用于在运行时将字符串添加到内部的字符串池中,并返回字符串池中的引用。

它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

返回值
当调用 intern() 方法时,如果字符串池中已经存在相同内容的字符串,则返回字符串池中的引用;否则,将该字符串添加到字符串池中,并返回对字符串池中的新引用。

public class RunoobTest {public static void main(String args[]) {String str1 = "Runoob";String str2 = new String("Runoob");String str3 = str2.intern();System.out.println(str1 == str2);  // falseSystem.out.println(str1 == str3);  // true}
}

优点
使用 intern() 方法可以在需要比较字符串内容时节省内存,因为它可以确保相同内容的字符串共享同一个对象。然而,过度使用 intern() 方法可能导致字符串池的增长,消耗大量内存。因此,应谨慎使用 intern() 方法,只在必要时使用。

3. 问题及反思

3.1 一人一单的并发安全问题

如果是集群模式下,会有多个tomcat,tomcat中的锁不共享。需要采用分布式锁才可以生效。
在这里插入图片描述

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

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

相关文章

字节跳动(社招)三面算法原题

TikTok 喘息 继上月通过强制剥离 TikTok 法案后&#xff0c;美国众议院在当地时间 20 日下午以 360 票赞成 58 票反对通过了新的法案&#xff1a;剥离 TikTok 的期限由生效后 165 天调整至 270 天之内&#xff0c;即今年 11 月的美国总统大选后。 之前我们讲过&#xff0c;TikT…

5款制作表格的软件,一键帮你实现数据可视化

数据可视化是许多企业决定未来方向、产品研发和用户研究的关键。只有大量的数据支持才能做出最明智的决定&#xff0c;因此表格在可视化中逐渐发挥着不可替代的作用。可以看出&#xff0c;掌握表格制作技巧是多么重要。然而&#xff0c;不能制作表格的小型合作伙伴不必担心。国…

Linux内核驱动开发-001字符设备开发-003独立按键杂项驱动

1驱动程序 /*************************************************************************> File Name: key_misc.c> Author: yas> Mail: rage_yashotmail.com> Created Time: 2024年04月22日 星期一 17时20分42秒**********************************************…

QT QZipReader改进,以支持大于2G的zip文件

QZipReader对ZIP文件读取非常方便好用。即使在最新版的QT 6.6.1里&#xff0c;仍然存在一些问题&#xff1a;对于大于2G的zip文件不支持。 虽然有标准zlib可调用&#xff0c;但包装成一个易用且功能成熟的zip解压功能库&#xff0c;还是有很大的工作量&#xff0c;也需要有一定…

交通工程绪论

一、交通工程 交通工程学定义交通工程学研究的内容交通工程学的产生与发展交通工程学在道路运输管理中的作用 1. 交通工程学定义 早在20世纪30年代&#xff0c;美国交通工程师协会(American Institute of Traffic Engineers)给交通工程学(Traffic Engineering)下了一个定义&a…

每日一题 — 二分查找

704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 朴素二分查找模板&#xff1a; while(.......){//防止溢出int mid left(right - left)/2;if(........){right mid-1;}else if(......){left mid1;}else{return mid;}} 代码&#xff1a; public int search(int[] num…

jdbc操作数据库 and 一个商品管理页面

文章目录 1. 介绍1.1 应用知识介绍1.2 项目介绍 2. 文件目录2.1 目录2.2 介绍以下&#xff08;从上到下&#xff09; 3. 相关代码3.1 DBConnection.java3.2 MysqlUtil.java3.3 AddServlet.java3.4 CommodityServlet.java3.5 DelectServlet.java3.6 SelectByIdServlet.java3.7 S…

揭秘Faiss:大规模相似性搜索与聚类的技术神器深度解析!

Faiss&#xff08;由Facebook AI Research开发&#xff09;是一个用于高效相似性搜索和密集向量聚类的库。它用C编写&#xff0c;并提供Python绑定&#xff0c;旨在帮助研究人员和工程师在大规模数据集上进行快速的相似性搜索和聚类操作。 一、介绍&#xff1a; Faiss的核心功…

双链向表专题

1.链表的分类 链表的种类非常多组合起来就有 2 2 8种 链表说明&#xff1a; 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常⽤还是两种结构&#xff1a; 单链表 和 双向带头循环链表 1. 无头单向⾮循环链表&#xff1a;结构简单&#xff0c;⼀般不会单独⽤来存数…

Ultralytics YOLOv8 英伟达™ Jetson®处理器部署

系列文章目录 前言 本综合指南提供了在英伟达 Jetson设备上部署Ultralytics YOLOv8 的详细攻略。此外&#xff0c;它还展示了性能基准&#xff0c;以证明YOLOv8 在这些小巧而功能强大的设备上的性能。 备注 本指南使用Seeed Studio reComputer J4012进行测试&#xff0c;它基于…

在邮件控件Aspose.Email中,处理Outlook TNEF 格式电子邮件

Microsoft Outlook 中常见的传输中性封装格式 (TNEF)电子邮件在处理和提取其内容时可能会带来挑战。在这篇博文中&#xff0c;我们将探讨如何使用强大的 .NET C# 库来处理此类消息&#xff0c;该库简化了各种电子邮件格式&#xff08;包括 TNEF&#xff09;的处理。 Aspose.Em…

尚硅谷-JavaSE阶段考试与面试题库

一、基础题 1&#xff09;用最有效的的方法算出2称以8等于几 答案&#xff1a;2<<3 2&#xff09;两个对象a和b&#xff0c;请问ab和a.equals(b)有什么区别&#xff1f; ab&#xff1a;比较对象地址 a.equals(b)&#xff1a;如果a对象没有重写过equals方法&#xff0c…

【技术干货】润石红外额温枪方案芯片功能介绍

手持红外额温枪框图中&#xff0c;以电池采用9V为例&#xff0c;先通过一个高压LDO RS3002 把电池电压转为3V&#xff0c;供整个系统使用&#xff0c;包括为 MCU&#xff0c;背光灯&#xff0c;运放 等器件供电&#xff0c;然后再用一个低功耗LDO RS3236 从3V 降为1.5V&#…

rc_visard 3D Stereo Senso

1 简介 rc_visard 3D立体视觉传感器 支持的接口标准 GenICam Generic Interface for CamerasGigE Gigabit Ethernet 词汇表 SGM semi-global matching 半全局匹配 SLAM Simultaneous Localization and Mapping 即时定位与地图构建 2 安全 3 硬件规格 坐标系 rc_visar…

TypeScript 中 interface 和 type 的使用#记录

一、interface&#xff1a;接口 interface A{label: string; }const aa ((aObj: A) > {console.log(aObj.label);//123return aObj.label; })aa({label: 123}) 1、可选属性 interface A{label: string;age?: number; } 2、只读属性 interface A{label: string;age?:…

231 基于matlab的北斗信号数据解析

基于matlab的北斗信号数据解析&#xff0c;多通道和单通道接收到的北斗信号数据&#xff0c;利用接收到的北斗数据&#xff08;.dat .txt文件&#xff09;&#xff0c;进行解析&#xff0c;得到初始伪距&#xff0c;平滑伪距&#xff0c;载波相位&#xff0c;并计算其标准差&am…

翱途开发平台新手上路-体验APP移动手机办公

O2OA(翱途)开发平台拥有配套的移动办公APP&#xff0c;支持IOS和安卓端&#xff0c;用户可在连接O2云之后&#xff0c;使用APP使用移动办公。移动办公APP开放源代码&#xff0c;不会产生任何费用。本篇主要简单讲述初如何完成服务器连接O2云&#xff0c;实现移动办公。 一、先决…

Barnes-Hut t-SNE:大规模数据的高效降维算法

在数据科学和分析中&#xff0c;理解高维数据集中的底层模式是至关重要的。t-SNE已成为高维数据可视化的有力工具。它通过将数据投射到一个较低维度的空间&#xff0c;提供了对数据结构的详细洞察。但是随着数据集的增长&#xff0c;标准的t-SNE算法在计算有些困难&#xff0c;…

什么是IoT?

什么是IoT&#xff1f; IoT&#xff0c;即物联网&#xff08;Internet of Things&#xff09;&#xff0c;是通过信息传感设备和互联网将各种物品连接起来&#xff0c;实现智能化的识别、定位、跟踪、监控和管理的网络系统。 以下是关于IOT的一些详细解释&#xff1a; 基本概…

JVM之本地方法栈和程序计数器和堆

本地方法栈 本地方法栈是为虚拟机执行本地方法时提供服务的 JNI&#xff1a;Java Native Interface&#xff0c;通过使用 Java 本地接口程序&#xff0c;可以确保代码在不同的平台上方便移植 不需要进行 GC&#xff0c;与虚拟机栈类似&#xff0c;也是线程私有的&#xff0c;…