基于秒杀-----分布式锁----lua脚本

这篇具有很好参考价值的文章主要介绍了基于秒杀-----分布式锁----lua脚本。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基于商品显示秒杀-一人一单业务_xzm_的博客-CSDN博客改进

基于秒杀-----分布式锁----lua脚本

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

 分布式锁的五个基本要求:多进程可见,互斥,高可用,高性能,安全性

三种实现方式

基于秒杀-----分布式锁----lua脚本

 redis

基于秒杀-----分布式锁----lua脚本

 1.创建获取锁删除锁的工具类

public interface ILock {

    /**
     * 获取锁
     * @param timeoutSec 自动超时时间
     * @return
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     */
    void unlock();

}
package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock{

    public String name;
    public StringRedisTemplate stringRedisTemplate;

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private static final String KEY_PREFIX="lock:";



    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        long thread = Thread.currentThread().getId();
        //获取锁
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, thread + "", timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(aBoolean);
    }

    @Override
    public void unlock() {
        //释放锁
        stringRedisTemplate.delete(KEY_PREFIX+name);
    }
}

2.修改代码

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
   @Resource
    private ISeckillVoucherService iSeckillVoucherService;
   @Resource
   private RedisIdWorker redisIdWorker;
   @Autowired
   private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //查询优惠卷
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
        //判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //秒杀尚未开始
            return Result.fail("秒杀尚未开始,请耐心等待");
        }
        //判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //秒杀已经结束
            return Result.fail("本次秒杀已经结束");
        }
        //秒杀处于正常时间段
        //判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("本次秒杀已经被抢完");
        }
        //库存充足

        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
        //对相同的id进行加锁
//        synchronized(userId.toString().intern()) {
            //获取代理对象
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            //在service中添加createVoucherOrder方法,也可利用idea代码修复功能自动添加
//            return proxy.createVoucherOrder(voucherId);
//            return createVoucherOrder(voucherId);
//        }

        //创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        //获取锁
        boolean tryLock = lock.tryLock(500);

        if (!tryLock) {
            //获取锁失败
            return Result.fail("每人仅限一单");
        }
        //获取锁成功

        try {
            return createVoucherOrder(voucherId);
        } finally {
            //释放锁
            lock.unlock();
        }


    }

    @Transactional
    public  Result createVoucherOrder(Long voucherId) {
        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
         /**
          * 进行判断,一人一单
          */
         int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
         if (count > 0) {
             //用户已经购买过商品
             return Result.fail("每位用户仅限购一单");
         }

         //扣减库存
         boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1")
                 .eq("voucher_id", voucherId)
                 .gt("stock", 0)
                 .update();
         //判断库存扣减是否成功
         if (!update) {
             //库存扣减失败,返回信息
             return Result.fail("库存不足");
         }
         //库存扣减成功,添加订单信息
         VoucherOrder voucherOrder = new VoucherOrder();
         //添加订单id
         //使用订单生成器生成id
         long id = redisIdWorker.nextId("order");
         voucherOrder.setId(id);
         //添加用户id

         voucherOrder.setUserId(userId);
         //添加消费卷id
         voucherOrder.setVoucherId(voucherId);
         //添加到数据库
         boolean save = this.save(voucherOrder);
         if (!save) {
             //添加失败
             return Result.fail("下单失败");
         }
         return Result.ok(id);
    }
}

测试结果:实现了多个服务器下的一人一单

现阶段存在问题:当线程阻塞时间超过setnx的自动过期时间时可能导致一人多单和setnx的key误删情况

基于秒杀-----分布式锁----lua脚本

 优化误删问题

思路:

基于秒杀-----分布式锁----lua脚本

 实现:

修改工具类

package com.hmdp.utils;

import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock{

    public String name;
    public StringRedisTemplate stringRedisTemplate;

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private static final String KEY_PREFIX="lock:";
    private static final String ID_PREFIX= UUID.fastUUID().toString(true)+"-";



    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String thread = ID_PREFIX+Thread.currentThread().getId();
        //获取锁
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, thread , timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(aBoolean);
    }

    @Override
    public void unlock() {
        //获取锁标识
        String thread = ID_PREFIX+Thread.currentThread().getId();
        //获取redis中的值
        String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (thread.equals(s)){
            //释放锁
            stringRedisTemplate.delete(KEY_PREFIX+name);
        }
    }
}

现阶段依旧存在误删除的问题

逻辑:基于秒杀-----分布式锁----lua脚本

 解决方法:让判断锁标识与释放锁保持原子性

Lua脚本

基于秒杀-----分布式锁----lua脚本

 解决方法

1.创建nulock.lua文件

2.编写lua脚本

-- 获取锁中的线程标识 get key
local  id=redis.call('get',KEYS[1])
--比较线程标识与锁中的标识是否一致
if id == ARGV[1] then
    --释放锁
    return redis.call('del',KEYS[1])
end
return 0

3.修改unlock方法

 //创建接收lua脚本
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    //在静态方法中初始化UNLOCK_SCRIPT
    static {
        UNLOCK_SCRIPT=new DefaultRedisScript<>();
        //设置接收lua脚本文件
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("nulock.lua"));
        //设置返回值类型
        UNLOCK_SCRIPT.setResultType(Long.class);
    }


    @Override
    public void unlock() {
        //使用lua脚本
        stringRedisTemplate.execute(UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX+Thread.currentThread().getId()
                );
    }

到目前为止已经可以做到生产可用

redis分布式锁的优化

需要优化的问题:不可重入,不可重试,超时释放,主从一致

基于秒杀-----分布式锁----lua脚本

 解决方法:使用redis的框架redisson实现分布式锁

基于秒杀-----分布式锁----lua脚本

 使用方法:

1.引入依赖

<!--        redis框架redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>

2.编写配置文件


@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        //配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.2.182:6379").setPassword("123456");
        //创建Redissonclient对象
        return Redisson.create(config);
    }
}

3.修改业务类(仅进行注入了RedissonClient 和创建锁对象和trylock的参数)

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.Voucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IVoucherService;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
   @Resource
    private ISeckillVoucherService iSeckillVoucherService;
   @Resource
   private RedisIdWorker redisIdWorker;
   @Autowired
   private StringRedisTemplate stringRedisTemplate;
    @Autowired
   private RedissonClient redissonClient;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //查询优惠卷
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
        //判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //秒杀尚未开始
            return Result.fail("秒杀尚未开始,请耐心等待");
        }
        //判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //秒杀已经结束
            return Result.fail("本次秒杀已经结束");
        }
        //秒杀处于正常时间段
        //判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("本次秒杀已经被抢完");
        }
        //库存充足

        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
        //对相同的id进行加锁
//        synchronized(userId.toString().intern()) {
            //获取代理对象
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            //在service中添加createVoucherOrder方法,也可利用idea代码修复功能自动添加
//            return proxy.createVoucherOrder(voucherId);
//            return createVoucherOrder(voucherId);
//        }

        //创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        //获取锁
        boolean tryLock = lock.tryLock();

        if (!tryLock) {
            //获取锁失败
            return Result.fail("每人仅限一单");
        }
        //获取锁成功

        try {
            return createVoucherOrder(voucherId);
        } finally {
            //释放锁
            lock.unlock();
        }


    }

    @Transactional
    public  Result createVoucherOrder(Long voucherId) {
        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
         /**
          * 进行判断,一人一单
          */
         int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
         if (count > 0) {
             //用户已经购买过商品
             return Result.fail("每位用户仅限购一单");
         }

         //扣减库存
         boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1")
                 .eq("voucher_id", voucherId)
                 .gt("stock", 0)
                 .update();
         //判断库存扣减是否成功
         if (!update) {
             //库存扣减失败,返回信息
             return Result.fail("库存不足");
         }
         //库存扣减成功,添加订单信息
         VoucherOrder voucherOrder = new VoucherOrder();
         //添加订单id
         //使用订单生成器生成id
         long id = redisIdWorker.nextId("order");
         voucherOrder.setId(id);
         //添加用户id

         voucherOrder.setUserId(userId);
         //添加消费卷id
         voucherOrder.setVoucherId(voucherId);
         //添加到数据库
         boolean save = this.save(voucherOrder);
         if (!save) {
             //添加失败
             return Result.fail("下单失败");
         }
         return Result.ok(id);
    }
}

原理:

基于秒杀-----分布式锁----lua脚本

 文章来源地址https://www.toymoban.com/news/detail-464507.html

到了这里,关于基于秒杀-----分布式锁----lua脚本的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性

    🎄 分布式锁:满足分布式系统或集群模式下 多进程可见 并且 互斥的 锁 🎄 分布式锁的核心是实现多进程之间锁的互斥 ,而满足这一点的方式有很多,常见的有三种: 🎄锁获取了,还没有来得及设置过期时间服务器就宕机了 🎄保证 setnx(获取锁)和 expire 设置过期时间两

    2024年02月15日
    浏览(40)
  • Redis+分布式+秒杀

    关于mysql关系型数据库的一些分析: 1、从性能上:如果查询结果不是很频繁变动的SQL语句,我们就没有必要每次都去查询数据库,可以把这种数据放在基于缓存的数据库中,这样不仅提升了查询效率还分担了数据库压力。 2、从并发上:在大并发的情况下(比如618秒杀活动,

    2024年02月06日
    浏览(47)
  • redis分布式秒杀锁

    2024年02月07日
    浏览(42)
  • 分布式秒杀方案--java

    前提:先把商品详情和秒杀商品缓存redis中,减少对数据库的访问(可使用定时任务) 秒杀商品无非就是那几步(前面还可能会有一些判断,如用户是否登录,一人一单,秒杀时间验证等) 1一人一单 2.判断库存 3.减库存 4.创建订单 1.1这样秒杀肯定会出现超卖的情况,所以必

    2024年02月09日
    浏览(39)
  • Golang实现Redis分布式锁解决秒杀问题

    先写一个脚本sql,插入2000个用户 登录是通过2个字段,一个是mobile,一个是password,生成了mobile从1到2000,密码默认是123456 然后写一个单元测试,实现新注册的2000个用户登录,然后获取token 我们使用有缓冲的通道和sync.WaitGroup信号量,来控制协程的数量,经过测试,发现limi

    2024年02月14日
    浏览(42)
  • Redis分布式锁原理之实现秒杀抢优惠卷业务

    背景 优惠券秒杀有两个业务涉及线程并发问题,第一个是库存超卖,第二个是一人一单,这就必须采取锁的方案了。下面根据优惠券秒杀功能一步一步进行展开,利用悲观锁、同步锁、分布式锁等方案循序渐进解决各种问题。 下单核心思路:当我们点击抢购时,会触发右侧

    2024年02月03日
    浏览(49)
  • 【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单

    🍀 id 字段不是 自增 AUTO_INCREMENT 的 每个店铺都可以发布优惠券: 用户抢购的时候会生成订单并保存到 tb_voucher_order 这张表中 如订单 id 使用数据库自增 ID 会出现以下问题: 🍀 id 规律性太明显(可能会被用户猜测到优惠券的 id) 🍀 受单表数据量的限制(优惠券订单可能很多

    2024年02月16日
    浏览(43)
  • 超全整理,Jmeter性能测试-脚本error报错排查/分布式压测(详全)

    性能脚本error报错问题排查 1、脚本运行过程中报错 1)在windows系统jmeter中,给测试脚本添加查看结果树,添加保存错误日志信息 第一步,给文件取名error.xml 第二步,仅错误日志 第三步,点击配置,全部勾选 2)把linux系统中保存的error日志文件,导入到windows系统jmeter查看结

    2024年02月07日
    浏览(55)
  • Apache Doris (八) :Doris分布式部署(五) Broker部署及Doris集群启动脚本

    目录 1.Broker部署及扩缩容 1.1 BROKER 部署 1.2 BROKER 扩缩容 2. Apache Doris集群启停脚本

    2024年02月11日
    浏览(41)
  • 【分布式训练】基于Pytorch的分布式数据并行训练

    简介: 在PyTorch中使用DistributedDataParallel进行多GPU分布式模型训练 加速神经网络训练的最简单方法是使用GPU,它在神经网络中常见的计算类型(矩阵乘法和加法)上提供了比CPU更大的加速。随着模型或数据集变得越来越大,一个GPU很快就会变得不足。例如,像BERT和GPT-2这样的

    2024年02月17日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包