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

news2024/11/26 12:34:30

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

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

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

三种实现方式

 redis

 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误删情况

 优化误删问题

思路:

 实现:

修改工具类

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脚本

 解决方法

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分布式锁的优化

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

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

 使用方法:

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);
    }
}

原理:

 

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

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

相关文章

《斯坦福数据挖掘教程·第三版》读书笔记(英文版) Chapter 9 Recommendation Systems

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT Chapter 9 Recommendation SystemsRecommendation systems use a number of different technologies. We can classify these systems into two broad groups. Content-based systems examine properties…

STL 函数对象,即仿函数

目录 1.函数对象 1.1函数对象的概念 1.2函数对象的使用 2.谓词 2.1谓词概念 2.2一元谓词 2.3二元谓词 3.内建函数对象 3.1内建函数对象的意义 3.2算术仿函数 3.3关系仿函数 3.4逻辑仿函数 1.函数对象 1.1函数对象的概念 1.2函数对象的使用 #include<iostream>…

文心一言 VS 讯飞星火 VS chatgpt (25)-- 算法导论4.2 7题

七、设计算法&#xff0c;仅使用三次实数乘法即可完成复数 abi和cdi 相乘。算法需接收a、b、c和d 为输入&#xff0c;分别生成实部 ac-bd 和虚部adbc。 文心一言&#xff1a; 可以使用如下算法来计算复数 abi 和 cdi 的积&#xff0c;且只需进行三次实数乘法&#xff1a; 1.…

Slower使用教程完整版本【2023年更新】

Slower软件的版本&#xff0c;目前市面上有多种。 如果你指的是Slower器加速软件的话&#xff0c;可以看下面的教程&#xff1a; Slower是一款很不错的安全国际互联网工具&#xff0c;广泛用于外贸与留学生行业&#xff0c;设计师行业与科研行业。但是&#xff0c;因为使用过…

注意:腾讯云轻量应用服务器十大限制说明

腾讯云轻量应用服务器相对于云服务器CVM是有一些限制的&#xff0c;比如轻量服务器不支持更换内网IP地址&#xff0c;不支持自定义私有网络VPC&#xff0c;内网连通性方面也有限制&#xff0c;轻量不支持CPU内存、带宽或系统盘单独升级&#xff0c;只能整个套餐整体升级&#x…

IO系统(计算机组成原理)

IO系统这一章主要讲的就是IO的四种控制方式&#xff0c;首先对这四种方式进行一个简单介绍&#xff0c;下面再对着四种方式分别进行介绍。 程序查询方式&#xff1a;由cpu通过程序不断查询IO设备是否已经做好准备&#xff0c;从而控制IO设备于主机进行信息交换 程序中断方式&am…

python自动化测试面试题,25K入职字节测试岗

问&#xff1a; http 和 https的区别   答&#xff1a; https需要申请ssl证书&#xff0c;https是超文本传输协议&#xff0c;信息是明文传输&#xff0c;https则是具有安全性的ssl加密传输协议http和https使用的是不同的链接方式&#xff0c;用的端口也不一样&#xff0c;前…

《深入理解计算机系统》读书笔记1

1.1信息就是位上下文 只由ASCLL字符构成的文件称为文本文件&#xff0c;所有其他文件都称为二进制文件。 系统中的所有的信息都由一串比特表示。区分不同数据对象的唯一方法是读到这些数据对象时的上下文。 1.2程序被其他程序翻译成不同的格式 预编译&#xff0c;编译&#xf…

【C++】类的访问权限

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01;时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、示例代码 3、总结 1、缘起 在 C 中&#xff0c;类在设计时&#xff0c;可以把 属性 和 行为 放在不同的权限下加以…

智警杯赛前学习1.1---excel基本操作

修改默认设置 步骤一&#xff1a;打开“Excel选项”窗口&#xff0c;打开“文件”菜单&#xff0c;选择“选项”标签 步骤二&#xff1a;在“Excel选项”窗口中&#xff0c;选择“常规与保存”标签&#xff0c;在“常规与保存”标签中&#xff0c;可以修改录入数据时的默认字体…

【群智能算法改进】一种改进的沙丘猫群优化算法 改进沙丘猫群算法 改进SCSO[1]【Matlab代码#34】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始沙丘猫群优化算法2. 改进沙丘猫群算法2.1 Logistic混沌映射种群初始化2.2 透镜成像折射反向学习策略2.3 动态因子2.4 黄金正弦策略 3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xf…

国际标准 ISO 11898 解读

从 1993 第一个版 CAN 国际标准&#xff08;ISO 11898:1993 和 ISO 11519-2&#xff09;发布至今&#xff0c;ISO 11898 逐渐被分割整合成了相互独立的 6 个部分。分别以 Part 1 ~ Part 6 来标识。在旧版本&#xff08;2003年之前&#xff09;中 ISO 11898 是通信速度为 5kbps…

二维笛卡尔坐标系下的角的概念

文章目录 参考环境笛卡尔坐标系二维笛卡尔坐标系三维笛卡尔坐标系 任意角角的静态定义角的动态定义二维笛卡尔坐标系下角的概念方向正角、负角及零角 象限角象限象限角 终边相同角圆心角终边相同角 参考 项目描述搜索引擎Google 、Bing百度百科首页韩庆波正负角佟大大还是ETT【…

前端学习---Vue(6)路由

一、前端路由的概念和原理 Hash地址与组件的对应关系。 Hash:url中#之后的都是Hash地址 location.hash 1.1 前端路由的工作方式 ① 用户点击了页面上的路由链接 ② 导致了 URL 地址栏中的 Hash 值发生了变化 ③ 前端路由监听了到 Hash 地址的变化 ④ 前端路由把当前 Hash…

doris分区、join

动态分区和临时分区 动态分区 旨在对表级别的分区实现生命周期管理(TTL)&#xff0c;减少用户的使用负担。 目前实现了动态添加分区及动态删除分区的功能。只支持 Range 分区。原理 在某些使用场景下&#xff0c;用户会将表按照天进行分区划分&#xff0c;每天定时执行例行任…

tidyverse中filter行筛选时缺失值存在的一个坑

大家好&#xff0c;我是邓飞&#xff0c;好久没有更新博客了&#xff0c;是因为好久没有进步了。 之前我认为鲁迅说的对&#xff0c;他在《野草》中写道&#xff1a;“当我沉默着的时候&#xff0c;我觉得充实&#xff1b;我将开口&#xff0c;同时感到空虚”。现在确切的情况…

msvcr90.dll丢失的解决方法

在使用计算机的过程中&#xff0c;我们时常会遇到一些问题&#xff0c;比如应用程序无法正常启动&#xff0c;提示msvcr90.dll文件丢失&#xff0c;这个问题困扰了许多计算机用户。那么&#xff0c;怎么才能解决这个问题呢&#xff1f; 首先&#xff0c;让我们先了解一下msvcr…

c语言编程练习题:7-65 字符串替换

#include <stdio.h>int main() {char c;while (scanf("%c", &c) 1 && c ! \n) {if (c > A && c < Z) {c Z - (c - A);}printf("%c", c);}return 0; }代码来自&#xff1a;https://yunjinqi.top/article/190

Spring:spring-web中DeferredResult执行过程分析

对于HTTP请求的处理&#xff0c;有时处理请求的时间较长&#xff0c;可能会采用异步处理方式来处理。一般常用的异步处理方式是采用DeferredResult&#xff0c;本文会简单分析一下spring-web的整个处理过程。 首先&#xff0c;提供一个简单的DeferredResult例子&#xff1a; R…

C++map和set

目录&#xff1a; 什么是关联式容器&#xff1f;键值对树形结构的关联式容器 set的概念multiset的使用pair和make_pair map的概念用“[]”实现统计水果的次数 multimap的使用 什么是关联式容器&#xff1f; 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比…