redis实战篇--优惠卷秒杀模块(主要是讲解自定义的不可重入锁)

全局id处理器

由于我们在用户下单的时候会产生订单的数据,但是我们又不能直接基于自增来进行设置id,所以我们需要存在一个订单的编码来进行存储
在这里插入图片描述
这里的全局id生成器就是基于时间戳和序列号来进行生成的,关于时间戳,我们首先肯定是需要获取到时间,至于这里的时间需要我们自己去定义了,这里我拿到的是2025年的时间作为初始时间,这里是从1970年开始计时的,调用toepochsecond是算出来从当时到现在的秒数,然后我再获取当前时间,这样两者相减的话就能获得时间戳,但是有人问了,珠宝珠宝,我要是在同一时间下的单的话咋办,主包告诉你还有序列号,这里我们将时间戳去左移32位的话空出来的就是序列号的位置,然后我们接着去获取序列号,这时候就··················e需要使用redis的自增来进行运算了,我们需要的就是去创建一个存储redis的自增的key,这里我们不能设置死你想啊,我要是公用同一个key的话我在后面进行自增和导出数据报表的时候我怎么进行划分是不是,我怎么直到我每天每个月的数据量是多少,是不是都混在一块了?
所以,这里我们需要指定key,我们可以传递一个prefix的前缀,然后再进行加上当天的日期进行转换的话是不是就可以了,
在这里插入图片描述
在这里插入图片描述
这里我们可以写一个测试类来看看我是用我的小线程的时候产生的id值
在这里插入图片描述
在这里插入图片描述

这里主要是看电脑的性能,这里主包的电脑在4s产生的id数量,但是我用了学校的超算绝对能应付类似淘宝的双十一那样的活动,不会出现订单产生不够的情况
那有人又要问了,主包主包你的生成器在哪里使用啊,有的兄弟有的,主包的生成器在产生订单的时候我在给订单号进行设置数据的时候可以使用到
接下来我们去实现关于订单业务的相关代码
在这里插入图片描述

这里主包给大家写一个流程图
在这里插入图片描述
首先肯定是需要查询优惠卷是不是存在,要是存在的话就直接进行下一步,还需要看看是不是开始,是不是已经结束,看看优惠卷的数量,都已经通过的话,我就可以操作数据库(这里大家先看一下,后面会优化的),这里需要自己去创建一个订单,然后自己去设置里面的属性,最后保存就行
在这里插入图片描述

解决超卖问题

但是这样写代码就会遇到我进行超卖的情况,
在这里插入图片描述

我在进行多线程并发的情况下我的优惠卷只有100张,但是我却卖出了166张,为什么呢?
在这里插入图片描述
就是我在执行业务的时候我需要去查询库存,但是我是使用的多线程是不是,我的第二个第三个线程在查询的时候是不是有可能在我第一个线程的业务还没有执行完毕的时候我就已经查询出来说你的优惠卷数量库存大于0呢?
对不对,这时候这么解决呢?我们来分析一下,第一种方式,我加上锁,这里的锁又分为乐观锁和悲观锁,什么叫乐观,悲观呢?
乐观就是我认为这种业务不需要加上锁,我自己进行判断一下就行,悲观就是我害怕,我需要锁住才行
这里用的是乐观锁,你想啊,我的业务问题是什么,是我在查询的时候是不是我的库存的问题?那我是不是要从库存的角度入手,我在修改数据库的时候是不是需要再次查询一下
在这里插入图片描述
这里就是去判断我自己去查询的时候是不是我的订单id一样

解决优惠卷一人一单的问题

现在我在创建第二个一样的用户进行抢一下,是能够抢到一样的优惠卷的,这肯定是不行的,是不是,我发放优惠卷的时候我需要的是让很多人拿到,不是让你一个人拿到卷是不是,这样肯定是需要解决的,我们怎么进行解决呢?
在这里插入图片描述
这时候我们正常的思路是不是去数据库种查询一下我的订单的数量,我要是数量大于0的话,我是不是就返回给前端一个信息说你已经又一张优惠卷了,不能再抢了,这时候就要解决一下这个问题。

在这里插入图片描述
存在问题:还是和之前一样,如果这个用户故意开多线程抢优惠券,那么在判断库存充足之后,执行一人一单逻辑之前,在这个区间如果进来了多个线程,还是可以抢多张优惠券的,那我们这里使用悲观锁来解决这个问题
使用悲观锁进行解决问题的话我们需要先设计锁,就是需要考虑更多,要是我的服务器宕机的话,我该怎么释放锁,这时候就到了redis上场的时候,我们可以使用redis里面的setnx操作,并且设置一下时间

package com.hmdp.utils;

/***
 * 解决在redis集群下面实现分布式锁
 * 锁的实现原理:
 * 1.尝试获取锁,获取成功则返回true,获取失败则返回false
 * 2.释放锁,释放锁成功则返回true,释放锁失败则返回false
 *
 */
public interface ILock {
    /**
     * 尝试获取锁
     * @param timeoutSec 锁的过期时间,单位秒
     * @return true代表获取锁成功,false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);
    /**
     * 释放锁
     */
    void unLock();
}

package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.utils.ILock;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
public class ILockImpl implements ILock {
    private static final String KEY_PREFIX = "lock:";
    private static   String name;
    /**
     * 尝试获取锁
     * @param timeoutSec 锁的过期时间,单位秒
     * @return true代表获取锁成功,false代表获取锁失败
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    public ILockImpl(StringRedisTemplate stringRedisTemplate,String  name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        UserDTO user = UserHolder.getUser();
        Long id = user.getId();
        long threadId = Thread.currentThread().getId();

        Boolean aBoolean = stringRedisTemplate
                .opsForValue()
                .setIfAbsent(KEY_PREFIX + name, id + "" + threadId, timeoutSec, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }
    /**
     * 释放锁
     */

    @Override
    public void unLock() {
         stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

这时候还是需要设计一下关于怎么进行获取锁,以及怎么进行释放锁,获取锁的话我肯定是要存储到redis当中的是不是,那我可你当时选哟设计唯一的key的才能进行识别,这里我们使用的是username以及uuid作为prefix来进行拼接,存储的值就是id,释放锁的话更简单就是直接删除
在这里插入图片描述
这里先去注入一个实现类,使用resource注解进行注入,讲到注入的话就跟大家探讨一下关于resource和autowire进行注入的区别,首先就是resource是rt的,但是autowire是基于spring框架写的,所以这是两者的第一个区别,然后就是我的resource默认的情况下是基于我类型后面跟着的名称来查找的,只有在名称找不到的情况下才是基于类型进行查找的,但是对于autowire就正好相反了,还有就是在spring中我要是有一个接口被两个类同时实现的话,我在进行注入的时候还是需要使用quilfied进行指定的,不然会报错
ok,聊多了,现在言归正传,我们在设计锁的时候要明白我们需要锁住的是什么,肯定是用户对吧,我不能让一个用户去抢多个对吧?所以我们需要基于用户的id来上锁,所以现在就是需要创建一个锁,然后去获取锁,一般我们是用sync那个锁,但是上了同一把锁之后我进去还是能够拿到票,所以我现在是基于自己设置的lock锁来上锁,这样我在实现完业务的时候就能够自己释放锁,这里需要注意的是我要使用事务来进行实现的话,需要让代理来执行
在这里插入图片描述
我们这里使用aopcontext来获取现在的代理对象,最后来执行方法

集群环境下的并发问题

当我们去启动多台tomcat的时候这时候nginx的负载均衡就能够得到应用
有关锁失效原因分析

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值