Redis的分布式锁问题(八)基于Redis的分布式锁

news2025/2/26 7:17:28

Redis的分布式锁问题(八)基于Redis的分布式锁

分布式锁 

什么是分布式锁? 

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

当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

传统单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。

但是在分布式系统,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁的由来。

成为分布式锁的必要条件

分布式锁的实现方案

分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,市面上常见大致有三种分布式锁的实现方案

关于MySQL锁机制可以看看这里

【图灵MySQL】深入理解MySQL事务隔离级别与锁机制_面向鸿蒙编程的博客-CSDN博客https://blog.csdn.net/weixin_43715214/article/details/127594907

基于Redis实现分布式锁

关于锁的两个级别操作

关于锁的两个级别操作就是——获取锁、释放锁

获取锁

互斥,要确保只能有一个线程后去锁!

添加锁,利用setnx的互斥特性 

setnx lock thread

添加锁的过期时间,避免服务宕机引起的死锁! 

expire lock 10

释放锁

手动释放 

del lock

超时释放 

expire命令添加了过期时间,等时间到了自动释放!

如果在添加过期时间后宕机了呢? 

在redis中,获取锁添加锁的命令可以一起执行,一条redis的命令中,是具有原子性的! 

最优的写法如下 

set lock thread1 nx ex 10

Redis分布式锁实现秒杀下单(初级版本)

代码实现 

编写ILock接口,定义锁  

/**
 * redis的分布式锁
 */
public interface ILock {

    /**
     * 尝试获取锁
     * @param timeoutSec 锁持有的超时时间,过期后自动释放
     * @return true代表获取锁成功; false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);

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

实现ILock接口,编写tryLock()和unLock()逻辑 

/**
 * redis的分布式锁
 * 实现ILock接口
 */
public class SimpleRedisLock implements ILock {

    // 不同的业务有不同的锁名称
    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

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

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁
        // set lock thread1 nx ex 10
        // nx : setIfAbsent , ex : timeoutSec
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        // 自动拆箱!!!可能有风险
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result seckillVoucher(Long voucherId) {

        // 1. 查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        LocalDateTime nowTime = LocalDateTime.now();

        // 2. 判断秒杀是否开始
        if (nowTime.isBefore(voucher.getBeginTime())) {
            return Result.fail("活动未开始!");
        }

        // 3. 判断秒杀是否结束
        if (nowTime.isAfter(voucher.getEndTime())) {
            return Result.fail("活动已结束!");
        }

        // 4. 判断库存
        if (voucher.getStock() < 1) {
            return Result.fail("已买完!");
        }

        Long userId = UserHolder.getUser().getId();

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

        if (!isLock) {
            return Result.fail("不允许重复下单!");
        }
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            lock.unlock();
        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        // 一人一单
        Long userId = UserHolder.getUser().getId();

        Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0){
            return Result.fail("用户已经购买过一次了!");
        }

        // 5. 减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).gt("stock", 0)  // CAS方案(乐观锁)!
                .update();

        if (!success) {
            return Result.fail("库存不足");
        }

        // 6. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();

        // 6.1 订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        // 6.2 用户id
        voucherOrder.setUserId(userId);
        // 6.3代金券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        return Result.ok(orderId);
    }
}

从之前的 synchronized 到现在的 isLock

// 创建锁对象

SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);

// 获取锁 1200s

boolean isLock = lock.tryLock(1200);

if (!isLock) {

        return Result.fail("不允许重复下单!");

}

try {

        IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();         return proxy.createVoucherOrder(voucherId);

} finally {

        lock.unlock();

测试结果 

 

存在问题

 

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

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

相关文章

springboot+vue3+ts实现一个点赞功能

前端&#xff1a;vitevue3tselementplusless 后端&#xff1a;springboot2.7.5mybatisplus 后端&#xff1a; 引入pom依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</…

通俗理解模拟退火算法,小白也看得懂!

模拟退火的实例 齐白石原是木匠&#xff0c;快30岁才正式学画。直到57岁以后画风开始转变&#xff0c;才真正有所成就。 鲁迅原本留日学医&#xff0c;25岁弃医从文&#xff0c;但37岁时才写出第一篇白话文小说《狂人日记》。 项羽在24岁巨鹿之战成名&#xff0c;26岁封王却走…

Advances in Graph Neural Networks笔记2:Fundamental Graph Neural Networks

诸神缄默不语-个人CSDN博文目录 本书网址&#xff1a;https://link.springer.com/book/10.1007/978-3-031-16174-2 本文是本书第二章的学习笔记。 我们学校没买这书&#xff0c;但是谷歌学术给我推文献时给出了一个能免登录直接上的地址&#xff0c;下次就不一定好使了&#…

Web of science,scopus,Google scholar的介绍和区别

Overview 最近为了和实验室同学分享这几个常见的数据库的区别&#xff0c;因此做了一个简单的 从这张图上面可以看到&#xff0c;Web of science (Wos)是在1997年&#xff0c;由Institute for Scientific Information (ISI)合并多种索引建立的&#xff0c;而ISI在1992年加入了…

11.21二叉树oj

目录 一.队列.栈顺序表总结 二.猫狗问题 三.股票价格跨度 四.二叉树的初始化 1.获取树中节点的个数 1.遍历思路 2.子问题思路 2.叶子节点个数 3.获取第K层节点的个数 4.获取二叉树的高度 6.判断一棵树是不是完全二叉树 一.队列.栈顺序表总结 1.顺序表和栈的底层都是…

C++ 单向链表手动实现(课后作业版)

单向链表&#xff0c;并实现增删查改等功能 首先定义节点类&#xff0c;类成员包含当前节点的值和下一个节点的地址 /node definition template <typename T> class Node { public:T value;Node<T>* next;Node() {}Node(const T& value) {this->value va…

ES6 入门教程 19 Generator 函数的语法 19.7 yield星号表达式

ES6 入门教程 ECMAScript 6 入门 作者&#xff1a;阮一峰 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录ES6 入门教程19 Generator 函数的语法19.7 yield* 表达式19 Generator 函数的语法 19.7 yield* 表达式 如果在 Generator 函数内部&…

windows域控批量创建账号方法

目录 一、收集信息 二、编写脚本 &#xff08;一&#xff09;新建aduser.ps1的powershell脚本 &#xff08;二&#xff09;New-Aduser命令详解 三、生产环境报错 &#xff08;一&#xff09;ConvertTo-SecureString &#xff08;二&#xff09;指定的账号已存在 &#xff…

debian11 安装后必备配置

debian11 安装后必备配置 运行环境&#xff1a;PVE v7.2-11 CT容器 系统版本&#xff1a;Debian-11-standard_11.3-1_amd64.tar.zst 启动信息 Debian GNU/Linux 11 debian tty1debian login: root Password: Linux debian 5.15.64-1-pve #1 SMP PVE 5.15.64-1 (Thu, 13 Oct …

【数据结构】堆的实现堆排序Top-K

文章目录一、堆的概念及结构二、堆实现&#xff08;1&#xff09;创建结构体&#xff08;2&#xff09;具体函数实现及解析1.0 交换函数1.1 堆的打印1.2 堆的初始化1.3 堆的销毁1.4 堆的插入1.5堆的向上调整算法1.6 堆的删除1.7堆的向下调整算法1.8 取堆顶的数据1.9 堆的数据个…

【力扣练习】找一个字符串中不含有重复字符的最长字串的长度

class Solution: def lengthOfLongestSubstring(self, s: str) -> int: # 哈希集合&#xff0c;记录每个字符是否出现过 occ set() n len(s) # 右指针&#xff0c;初始值为 -1&#xff0c;相当于我们在字符串的左边界的左侧&#xff…

【项目实战】Spring Boot项目抵御XSS攻击

本专栏将为大家总结项目实战相关的知识&#xff01; 点击即可关注本专栏&#xff0c;获取更多知识&#xff01; 文章目录前言一、什么是XSS攻击二、如何抵御XSS攻击三、实现抵御XSS攻击结语前言 作为Web网站来说&#xff0c;抵御XSS攻击是必须要做的事情&#xff0c;这是非常常…

C++基础知识

目录 C的基本使用 C数据的输入与输出 C使用命令行 具体案例 C生成随机数 关键字 标识符命名规则 数据类型 整形 实型&#xff08;浮点型&#xff09; 浮点型变量分为2种 表示小数的两种方式 案例演示 字符型 案例演示 字符串类型 两种风格 两种风格字符串之间…

【MyBatis】MyBtis入门程序

1. 目录结构 2. 数据库表的设计 /*Navicat Premium Data TransferSource Server : MysqlSource Server Type : MySQLSource Server Version : 50726Source Host : localhost:3306Source Schema : mybatisTarget Server Type : MySQLTarget Se…

python_循环

一、while循环的基础语法程序中的循环&#xff1a;while 条件&#xff1a;条件满足时&#xff0c;做的事情1条件满足时&#xff0c;做的事情2......即只要条件满足&#xff0c;会无限循环执行代码示例&#xff1a;# 简单示例&#xff1a;向Vivian表白100次i 0 while i < 10…

RabbitMQ系列【13】优先级队列

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录前言1. 设置优先级队列2. 消息设置优先级前言 RabbitMQ将消息写入队列中都是按顺序写的&#xff0c;消费时也是按顺序进行消费&#xff0c;队列中的消息是先进先出(FIFO).。 首先测试一下没有优…

多数银行人都会忽略5个影响系统性能的因素总结

性能测试往往在投产上线前开展&#xff0c;无法对整个系统变更进行全面的覆盖测试&#xff0c;因此性能测试需求提出十分关键。 性能测试需求交付过程中&#xff0c;需要对开发团队提出的测试需求进行审查&#xff0c;重点分析交付的测试需求是否充分覆盖了影响系统性能的因素…

【OpenCV-Python】教程:3-7 Canny边缘检测

OpenCV Python Canny 边缘检测 【目标】 Canny 边缘检测的概念cv2.Canny 【原理】 1. 去噪 由于边缘检测非常容易收到图像的噪声影响&#xff0c;第一步使用 5x5 高斯滤波去除图像中的噪声。 2. 寻找图像的亮度梯度 在平滑后&#xff08;去噪后&#xff09;的图像利用 S…

1.5 阻塞与非阻塞I/O

文章目录1、阻塞I/O2、非阻塞I/O3、异步I/O4、同步I/O5、epoll原理函数5.1、int epoll_create(int size)5.2、int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event)5.3、int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)5.4、内核…

【Linux 线程介绍】

Linux 线程线程一定越多越好吗&#xff1f;线程的实现方式&#xff1a;API:pthread_exit函数演示获取线程的返回值多线程的不安全性查看进程中的线程数进程&#xff1a;一个正在运行的程序 &#xff0c;资源分配的基本单位 线程&#xff1a;进程内部的一条执行序列&#xff08;…