12.商品秒杀

秒杀流程

秒杀技术实现核⼼思想是运⽤缓存减少数据库瞬间的访问压⼒!读取商品详细信息时运⽤缓存,当⽤户点击抢购时减少缓存中的库存数量,当库存数为0时或活动期结束时,同步到数据库。 产⽣的秒杀预订单也不会⽴刻写到数据库中,⽽是先写到缓存,当⽤户付款成功后再写⼊数据库
在这里插入图片描述

秒杀商品压⼊缓存

在这里插入图片描述
秒杀商品列表和秒杀商品详情都是从Redis中取出来的,所以我们⾸先要将符合参与秒杀的商品定时查询出来,并将数据存⼊到Redis缓存中。数据存储类型我们可以选择Hash类型

 <!-- redis 使用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

定时任务

在这里插入图片描述

    @Autowired
    private RedisTemplate redisTemplate;

	 /**
     * 定时任务:每个30秒执行一次
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void loadGoodsPushRedis() {
   		//获取数据并存入redis
        redisTemplate.boundHashOps(SystemConstants.SEC_KILL_GOODS_PREFIX + extName).put(seckillGood.getId(), seckillGood);
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多线程抢单

在这里插入图片描述
在审视秒杀中,操作⼀般都是⽐较复杂的,⽽且并发量特别⾼,⽐如,检查当前账号操作是否已经秒杀过该商品,检查该账号是否存在存在刷单⾏为,记录⽤户操作⽇志等。
下订单这⾥,我们⼀般采⽤多线程下单,但多线程中我们⼜需要保证⽤户抢单的公平性,也就是先抢先下单。我们可以这样实现,⽤户进⼊秒杀抢单,如果⽤户复合抢单资格,只需要记录⽤户抢单数据,存⼊队列,多线程从队列中进⾏消费即可,存⼊队列采⽤左压,多线程下单采⽤右取的⽅式。

实现异步方法

在这里插入图片描述
需要实现异步的方法上加上@Async即可,底层是多线程

 @Async
 public void createOrder() {}

排队下单

在这里插入图片描述

@Data
public class SeckillStatus implements Serializable {

    //秒杀用户名
    private String username;
    //创建时间
    private Date createTime;
    //秒杀状态  1:排队中,2:秒杀等待支付,3:支付超时,4:秒杀失败,5:支付完成
    private Integer status;
    //秒杀的商品ID
    private Long goodsId;

    //应付金额
    private Float money;

    //订单号
    private Long orderId;
    //时间段
    private String time;
 }
 //将秒杀排队信息,leftpush存入redis的list队列中,保证用户先进先出
redisTemplate.boundListOps(SystemConstants.SEC_KILL_USER_QUEUE_KEY).leftPush(seckillStatus);
 SeckillStatus seckillStatus = (SeckillStatus) redisTemplate.boundListOps(SystemConstants.SEC_KILL_USER_QUEUE_KEY).rightPop();

防⽌秒杀重复排队

 /*
            用户秒杀排队次数 -> namespace = UserQueueCount
                                - username 次数
         */
Long userQueueCount = redisTemplate.boundHashOps(SystemConstants.SEC_KILL_QUEUE_REPEAT_KEY).increment(username, 1);

分布式锁(超卖)

超卖问题,这⾥是指多⼈抢购同⼀商品的时候,多⼈同时判断是否有库存,如果只剩⼀个,则都会判断有库存,此时会导致超卖现象产⽣,也就是⼀个商品下了多个订单的现象,解决超卖问题可以使⽤分布式锁得⽅案
在这里插入图片描述
上图可以看到,变量A存在JVM1、JVM2、JVM3三个JVM内存中(这个变量A主要体现是在⼀个类中的⼀个成员变量,是⼀个有状态的对象,例如:UserController控制器中的⼀个整形类型的成员变量),如果不加任何控制的话,变量A同时都会在JVM分配⼀块内存,三个请求发过来同时对这个变量操作,显然结果是不对的!即使不是同时发过来,三个请求分别操作三个不同JVM内存区域的数据,变量A之间不存在共享,也不具有可⻅性,处理的结果也是不对的

分布式锁具备哪些条件

在这里插入图片描述

基于数据库实现分布式锁

基于数据库的实现⽅式的核⼼思想是:在数据库中创建⼀个表,表中包含⽅法名等字段,并在⽅法名字段上创建唯⼀索引,想要执⾏某个⽅法,就使⽤这个⽅法名向表中插⼊数据,成功插⼊则获取锁,执⾏完成后删除对应的⾏数据释放锁
在这里插入图片描述
想要执⾏某个⽅法,就向表中插⼊数据

INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');

因为我们对 method_name 做了唯⼀性约束,这⾥如果有多个请求同时提交到数据库的话,数据库会保证只有⼀个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该⽅法的锁,可以执⾏⽅法体内容。
成功插⼊则获取锁,执⾏完成后删除对应的⾏数据释放锁

delete from method_lock where method_name ='methodName';

在这里插入图片描述

基于数据库排他锁

除了可以通过增删操作数据表中的记录以外,其实还可以借助数据库中⾃带的锁来实现分布式的锁。我们还⽤刚刚创建的那张数据库表。可以通过数据库的排他锁来实现分布式锁。 基于MySql的InnoDB引擎,可以使⽤以下⽅法来实现加锁操作
在这里插入图片描述
在查询语句后⾯增加 for update ,数据库会在查询过程中给数据库表增加排他锁(这⾥再多提⼀句,InnoDB引擎在加锁的时候,只有通过索引进⾏检索的时候才会使⽤⾏级锁,否则会使⽤表级锁。这⾥我们希望使⽤⾏级锁,就要给method_name添加索引,值得注意的是,这个索引⼀定要创建成唯⼀索引,否则会出现多个重载⽅法之间⽆法同时被访问的问题。重载⽅法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程⽆法再在该⾏记录上增加排他锁。

我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执⾏⽅法的业务逻辑,执⾏完⽅法之后,再通过以下⽅法解锁:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

基于Redis的实现⽅式
  • Redis有很⾼的性能;
  • Redis命令对此⽀持较好,实现起来⽐较⽅便

在这里插入图片描述

  • 获取锁的时候,使⽤setnx加锁,并使⽤expire命令为锁添加⼀个超时时间,超过该时间则⾃动释放锁,锁的value值为⼀个随机⽣成的UUID,通过此在释放锁的时候进⾏判断。
  • 获取锁的时候还设置⼀个获取的超时时间,若超过这个时间则放弃获取锁。
  • 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执⾏delete进⾏锁释放。

在这里插入图片描述

基于ZooKeeper的实现⽅式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以直接使⽤zookeeper第三⽅库Curator客户端,这个客户端中封装了⼀个可重⼊的锁服务
在这里插入图片描述
在这里插入图片描述

分布式锁总结

在这里插入图片描述

使用redis解决超卖问题(Redisson)

Redisson是Redis官⽅推荐的Java版的Redis客户端。它提供的功能⾮常多,也⾮常强⼤,此处我们只⽤它的分布式锁功能,来避免超卖问题

      <!-- redis 使用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.14.0</version>
        </dependency>
spring:
  redis:
    host: 192.168.220.110
    port: 6379

    @Autowired
    private RedissonClient redissonClient;

	@Async
    public void createOrder() {
            //分布式锁
            RLock lock = redissonClient.getLock("seckillstock:" + id);
            try {
                //获得锁
                //waitTime:等待锁的时间
                //leaseTime:所得持有时间
                lock.tryLock(20, 20, TimeUnit.SECONDS);
                //秒杀业务
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            	//释放锁
                lock.unlock();
            }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值