Redis---------实现商品秒杀业务,包括唯一ID,超卖问题,分布式锁

本文主要是介绍Redis---------实现商品秒杀业务,包括唯一ID,超卖问题,分布式锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 订单ID必须是唯一

59e1602b09834e0aadaa9602bbc8a20f.png

 唯一ID构成:

e0304f55e1f348838355b3126378a22d.png

代码生成唯一ID:


import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;//基于redis自增长的生成策略
@Component
public class RedisUUID {//起始时间时间秒数private static final long BEGIN_TIMESTAMP=1640995200L;//使用Redis自增策略private StringRedisTemplate stringRedisTemplate;public RedisUUID(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}//参数是业务的类型public long nextid(String keyType){//1,生成时间戳LocalDateTime now = LocalDateTime.now();long nowsecend = now.toEpochSecond(ZoneOffset.UTC);//当前时间的秒数减去起始时间的秒数得到时间戳long nowtime_stamp = nowsecend - BEGIN_TIMESTAMP;//2,生成序列号String nowdate = now.format(DateTimeFormatter.ofPattern("yyyy:mm:dd"));//使得每天都会生成新的一轮IDlong count = stringRedisTemplate.opsForValue().increment("icr:" + keyType + ":" + nowdate);//3,拼接返回return nowtime_stamp << 32 | count;}
}

827128d84da44b4793c030401783ed74.png

 

商品下单操作

业务逻辑:

7700ca73332d48708595361f1ae99dc3.jpg

 思路:主要是要了解以及掌握整个业务的流程:①先看商品是不是在秒杀的时间范围内②然后还要去看库存中是否还有该商品③如果有的话就扣减库存④然后就会生成订单,订单ID为唯一ID⑤把订单写入数据库中,再返回数据给前端

代码实现:

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService iSeckillVoucherService;@Autowiredprivate RedisUUID redisUUID;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {//1,查询商品的信息SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);//2,看是否在秒杀时间范围内if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("尚未开始!");}if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("已经结束啦!");}//3,再看库存是否还有if (voucher.getStock()<1) {return Result.fail("库存不足!");}//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if (!sucess) {return Result.fail("库存不足!");}//5,然后就创建订单信息VoucherOrder voucherOrder = new VoucherOrder();//5.1,订单id----id生成器long order = redisUUID.nextid("order");voucherOrder.setVoucherId(order);//5.2,用户idLong id = UserHolder.getUser().getId();voucherOrder.setUserId(id);//5.3,商品idvoucherOrder.setVoucherId(voucherId);//6,保存进数据库save(voucherOrder);//7,返回数据return Result.ok(order);}
}

 

库存超卖问题

先看看什么是库存超卖问题:

正常情况:

ec6c0f2b42804805a77a24f6e75ce430.jpg

 但是涉及到高并发的时候一定会出问题:

b46184b741a443fdbfa8f540833db7cf.jpg

所以我们要想办法去解决这个问题,锁!!!

ee298fbbb6684e16bc4411dcc7c90c40.jpg

 悲观锁认为一定发生并发问题,所以每一次操作都会加锁,是线程串行进行,不会出现并发问题,但是这样的话就导致性能降低,所以我们使用乐观锁,乐观锁是先让你操作,等你要修改数据库的时候再判断与你查到的数据是否是一样,如果是一样的才可以修改,否则不可以减库存。

乐观锁的两种实现判断法:

第一种:版本号法,就是通过查询两次版本号来判断是否被修改过库存

ac6f979be91d4cba9c36fff6f96e61a8.jpg

第二种:CAS法,是在版本号法上做的改进方法,既然要判断两次版本是否相同,为啥不判断库存量是否相同呢,所以CSA法就是去判断前后两次查询到的库存量是否一样,如果一样就可以改

c859dfee73f44fd9844699936966ab0c.jpg

用乐观锁CAS法来解决超卖问题:

//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update();if (!sucess) {return Result.fail("库存不足!");}

但是这样任然还不能解决超卖问题,因为如果两个线程同时来查到100,线程1做完修改还剩99,线程2查到不是100就会不执行修改,这样也会有问题,所以又要进行改进策略

 //4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock",0).update();if (!sucess) {return Result.fail("库存不足!");}

一人一单问题

 使用悲观锁处理单体服务下的多线程问题:

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService iSeckillVoucherService;@Autowiredprivate RedisUUID redisUUID;@Overridepublic Result seckillVoucher(Long voucherId) {//1,查询商品的信息SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);//2,看是否在秒杀时间范围内if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("尚未开始!");}if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("已经结束啦!");}//3,再看库存是否还有if (voucher.getStock()<1) {return Result.fail("库存不足!");}//实现单体服务下的一人一单的多线程安全问题Long id = UserHolder.getUser().getId();//先获取锁,再提交事务,保证线程安全synchronized (id.toString().intern()){//获得Spring的代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactionalpublic Result createVoucherOrder(Long voucherId) {//一人一单问题Long id = UserHolder.getUser().getId();Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();if(count > 0){return Result.fail("你已经购买过!");}//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock",0).update();if (!sucess) {return Result.fail("库存不足!");}//5,然后就创建订单信息VoucherOrder voucherOrder = new VoucherOrder();//5.1,订单id----id生成器long order = redisUUID.nextid("order");voucherOrder.setVoucherId(order);//5.2,用户idvoucherOrder.setUserId(id);//5.3,商品idvoucherOrder.setVoucherId(voucherId);//6,保存进数据库save(voucherOrder);//7,返回数据return Result.ok(order);}
}

 添加依赖:

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

 在启动类上添加:

@EnableAspectJAutoProxy(exposeProxy = true)

分布式集群模式下的多线程问题:

当我们是处理分布式集群模式下,两个JVM不是共用一把锁,导致每个JVM都有自己的锁导致我们之前的锁锁不住,每个JVM都有一个线程会获得锁。

 

 分布式锁:满足分布式系统或者集群模式下多进程可见并且互斥的锁

 

 

 基于Redis实现分布式锁:

创建锁对象:
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock{private String name;private StringRedisTemplate stringRedisTemplate;private static final  String KEY_PREFXY="lock:";public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean trylock(long timeoutSec) {//获取线程ID作为标识long ThreadId = Thread.currentThread().getId();//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFXY + name, ThreadId + "", timeoutSec, TimeUnit.MINUTES);//避免空指针return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {stringRedisTemplate.delete(KEY_PREFXY + name);}
}
代码实现Redis分布式锁的应用:

①先创建锁的对象,然后先是去获取锁②没有获取到锁就直接返回错误③获取到锁就可以进行对数据库的操作④操作完之后进行释放锁

Long id = UserHolder.getUser().getId();//创建锁对象SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order" + id, stringRedisTemplate);//获取锁boolean trylock = simpleRedisLock.trylock(1200);//判断是否获得锁成功if (!trylock) {//获取锁失败return Result.fail("不允许重复下单!");}//获得Spring的代理对象(事务)try {IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁simpleRedisLock.unlock();}

 但是就上面的处理还不够严谨,因为如果一个线程发生阻塞的话,其他线程可能会获得锁并且释放锁,导致锁误删问题,

解决锁误删问题:
import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock{private String name;private StringRedisTemplate stringRedisTemplate;private static final  String KEY_PREFXY="lock:";//得到一个唯一锁的标识private static final  String ID_PREFXY= UUID.randomUUID(true)+"-";public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean trylock(long timeoutSec) {//获取线程标识String ThreadId = ID_PREFXY+Thread.currentThread().getId();//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFXY + name, ThreadId, timeoutSec, TimeUnit.MINUTES);//避免空指针return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {//获取线程标识String ThreadId = ID_PREFXY+Thread.currentThread().getId();//判断要来修改的进程跟锁的标识是否一致String s = stringRedisTemplate.opsForValue().get(KEY_PREFXY + name);if(ThreadId.equals(s)){//释放锁stringRedisTemplate.delete(KEY_PREFXY + name);}}
}

这篇关于Redis---------实现商品秒杀业务,包括唯一ID,超卖问题,分布式锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/958956

相关文章

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM