【遇见青山】项目难点:集群下的分布式锁问题

news2024/11/15 11:27:30

【遇见青山】项目难点:集群下的分布式锁问题

  • 1.问题简介
  • 2.分布式锁分析
  • 3.基于Redis实现分布式锁1.0版本
  • 4.基于Redis实现分布式锁2.0版本,解决锁误删问题
  • 5.基于Redis实现分布式锁3.0版本,解决锁的原子性问题

1.问题简介

《【遇见青山】项目难点:解决超卖问题》一文中,我们使用了synchronized方法解决了单机系统下的超卖问题,但是在集群的情况下,这种锁就会失效

本质原因是集群状态下,不同的服务器的jvm不一致,由不同的锁监视器来控制锁的生成和释放,导致锁失效!依然会存在超卖问题,那么如何解决这个问题呢?

这就需要用到分布式锁🔒了!


2.分布式锁分析

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

本方案介绍基于Redis的分布式锁:

实现分布式锁时需要实现的两个基本方法:

在这里插入图片描述

业务流程图:

在这里插入图片描述


3.基于Redis实现分布式锁1.0版本

public class SimpleRedisLock implements ILock {

    private String name;

    private StringRedisTemplate stringRedisTemplate;

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

    /**
     * 尝试获取锁
     *
     * @param timeoutSec 锁的过期时间,防止死锁
     * @return 是否获取成功锁🔒
     */
    @Override
    public boolean tryLock(long timeoutSec) {

        // 获取线程标识
        long threadId = Thread.currentThread().getId();

        // 获取锁
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY + name, threadId + "", timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(success);
    }

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

在创建新的订单之前,手动获取锁和释放锁:

// 创建锁对象
SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
// 获取锁
boolean isLock = lock.tryLock(5);
// 获取锁失败,代表当前用户在多次抢券
if (!isLock) {
    return Result.fail("您已经抢过了哦~");
}

try {
    // 为了防止事务失效,这里使用代理对象调用方法
    IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
    // 返回订单id
    return proxy.createVoucherOrder(voucherId);
} finally {
    // 手动释放锁
    lock.unlock();
}

4.基于Redis实现分布式锁2.0版本,解决锁误删问题

锁误删问题,是指当线程1陷入阻塞时,阻塞时间大于锁的过期时间导致锁被提前释放,当线程1脱离阻塞时,执行了释放锁的操作,但是此时释放的锁可能时其他线程的锁,所以在各个线程进行锁释放时要判断释放的到底是不是自己的锁!

在这里插入图片描述

业务流程图:

在这里插入图片描述

接下来来改进一下1.0的代码:

在这里插入图片描述

public class SimpleRedisLock implements ILock {

    private String name;

    private StringRedisTemplate stringRedisTemplate;


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


    // 分布式锁的线程标识前缀,防止多个虚拟机线程ID一致,存入同样的redis分布式锁key中的巧合
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    /**
     * 尝试获取锁
     *
     * @param timeoutSec 锁的过期时间,防止死锁
     * @return 是否获取成功锁🔒
     */
    @Override
    public boolean tryLock(long timeoutSec) {

        // 获取线程标识,随机UUID + 线程id
        String threadId = ID_PREFIX + Thread.currentThread().getId();

        // 获取锁
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY + name, threadId + "", timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(success);
    }

    /**
     * 释放锁
     */
    @Override
    public void unlock() {
        // 获取线程标识,随机UUID + 线程id
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取redis分布式锁中的标识
        String lockId = stringRedisTemplate.opsForValue().get(LOCK_KEY + name);
        // 判断标识是否一致(是否是自己的锁🔒)
        if (threadId.equals(lockId)) {
            // 释放锁
            stringRedisTemplate.delete(LOCK_KEY + name);
        }
    }
}

5.基于Redis实现分布式锁3.0版本,解决锁的原子性问题

解决锁的原子性问题是指:当一个线程在执行完判断锁标识之后,准备释放锁之前,恰好发生了阻塞导致锁超时释放,这时如果脱离阻塞状态,由于还没有执行释放锁的操作,导致误释放其他线程的锁!

这时可以使用Lua脚本解决多条命令执行的问题

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言🫁

在这里插入图片描述

编写Lua脚本:unlock.lua

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

改进程序,首先在静态初始化时加载Lua脚本:

// 引入Lua脚本
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

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

其次,该进释放锁的代码:

/**
 * 释放锁
 */
@Override
public void unlock() {
    // 调用Lua脚本解决释放锁的原子性问题
    stringRedisTemplate.execute(UNLOCK_SCRIPT,
            Collections.singletonList(LOCK_KEY + name),
            ID_PREFIX + Thread.currentThread().getId());

}

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

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

相关文章

Sliver取代Cobalt Strike成黑客渗透工具“新宠”

8月25日消息&#xff0c;攻击者逐渐弃用Cobalt Strike渗透测试套件&#xff0c;转而使用不太知名的类似框架。开源跨平台工具Sliver正取代Brute Ratel成为受攻击者青睐的武器。 在过去的几年里&#xff0c;Cobal Strike被各类攻击者滥用&#xff08;包括勒索软件操作&#xff…

行为型模式 - 模板方法模式Template Method

学习而来&#xff0c;代码是自己敲的。也有些自己的理解在里边&#xff0c;有问题希望大家指出。 模式的定义与特点 模板方法&#xff08;Template Method&#xff09;&#xff0c;模式的定义如下&#xff1a;定义一个操作中的算法骨架&#xff0c;而将算法的一些步骤延迟到子类…

JavaSE XML解析技术的使用详解

文章目录XML解析技术XML解析技术介绍Dom4j解析XML文件Dom4j解析各个节点Dom4j解析案例实战XML解析技术 XML解析技术介绍 XML的数据作用是什么? 最终需要怎样处理? 作用: 存储数据、做配置信息、进行数据传输。 最终需要被程序进行读取&#xff0c;解析里面的信息。 XML解析…

【路径规划】基于A*算法和Dijkstra算法的路径规划(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Python蓝桥杯训练:基本数据结构 [链表]

Python蓝桥杯训练&#xff1a;基本数据结构 [链表] 文章目录Python蓝桥杯训练&#xff1a;基本数据结构 [链表]一、链表理论基础知识二、有关链表的一些常见操作三、力扣上面一些有关链表的题目练习1、[移除链表元素](https://leetcode.cn/problems/remove-linked-list-element…

TCP报头详解及TCP十种核心机制(一)

目录 前言&#xff1a; TCP报头 TCP核心机制 一、确认应答 二、超时重传 小结&#xff1a; 前言&#xff1a; 这篇文章详细介绍了TCP报头中的一些核心数据&#xff0c;及两种TCP核心机制。其他的一些机制会在后面文章中详细介绍。 TCP报头 解释&#xff1a; 1&#xff…

电商仓储与配送云仓是什么?

仓库是整个供给链的关键局部。它们是产品暂停和触摸的点&#xff0c;耗费空间和时间(工时)。空间和时间反过来也是费用。经过开发数学和计算机模型来微调仓库的规划和操作&#xff0c;经理能够显著降低与产品分销相关的劳动力本钱&#xff0c;进步仓库空间应用率&#xff0c;并…

docker/docker-compose 安装mysql5.7

目录使用docker安装mysql5.7docker普通安装docker生产环境安装使用docker-compose 安装注意注意一:docker-compose权限问题注意二:docker pull 找不到镜像使用docker安装mysql5.7 docker普通安装 docker pull mysql:5.7 # 启动容器 docker run -p 3306:3306 --name mysql -e …

数组和对象的拷贝(复制)

复制必须要产生新的对象。以下代码不是复制。 const arr ["孙悟空", "猪八戒", "沙和尚"]const arr2 arr // 不是复制&#xff0c;只是将arr的值赋给arr2&#xff0c;他们指的还是一个对象console.log(arr) // 二者输出一样 console.log(…

数楼梯(加强版)

数楼梯(加强版) 题目背景: 小明一天放学回家,看到从1楼到2楼共有n个台阶,因为好奇,他想尝试一下总共有几种方案到二楼?他可以1步,2步,3步的跳,不能跳3步以上. 他试了很多次都没有解决这个问题,于是请求聪明的你帮忙解决这个问题. 题目描述: 1楼到2楼楼梯有n级台阶。小明每…

Learning C++ No.8【内存管理】

引言&#xff1a; 北京时间&#xff1a;2023/2/12/18:04&#xff0c;昨天下午到达学校&#xff0c;摆烂到现在&#xff0c;该睡睡&#xff0c;该吃吃&#xff0c;该玩玩&#xff0c;在一顿操作之下&#xff0c;目前作息调整好了一些&#xff0c;在此记录&#xff0c;2月11&…

C++基础(6) - 复合类型(下)

文章目录指针1、指针概述1.1 存储器和存储地址空间1.2 内存地址1.3 指针和指针变量2、声明和初始化指针变量2.1 指针变量的声明2.2 指针变量的初始化3、使用指针变量3.1 解除引用3.2 野指针和空指针4、指针的宽度和跨度4.1 自身类型和指向类型4.2 指针变量所取内容的宽度4.3 指…

chatGPT会是银弹吗

chatGP最近火的一塌糊涂&#xff0c;它通过语言生成技术和自然语言处理能力&#xff0c;帮助用户快速解决问题并生成内容。目前&#xff0c;这款工具现在已经拥有超过一亿的活跃用户&#xff0c;并且因其高效率和易用性而受到了广大用户的好评。 不过谷歌可就倒霉了&#xff0c…

Shells:一款功能强大的反向Shell快速生成工具

关于Shells Shells是一款功能强大的反向Shell快速生成工具&#xff0c;该工具由4ndr34z负责开发和维护&#xff0c;可以帮助广大研究人员轻松生成常用的反向Shell。如果你需要一种简单的方法来生成格式化的PowerShell以及Python反向Shell的话&#xff0c;Shells这款工具将是你…

【IPD】敏捷开发与IPD结合的实践培训课程「3月11-12日」

课程名称敏捷开发与 IPD结合的实践 (Agile Development - IPD and Agile Development Practice &#xff09;参加对象企业总工、技术总监、系统架构师、研发经理、测试经理、质量/品质经理、研发测试骨干&#xff0c;以及研发测试技术人员。课程背景软件系统的日益复杂化和用户…

C语言学习笔记-内存管理

这篇将讲解 C 中的动态内存管理。C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。 序号函数和描述1void calloc(int num, int size);在内存中动态地分配 num 个长度为 size 的连续空间&#xff0c;并将每一个字节都初始化为 0。所以…

2023的金三银四,测试员还能找到好工作吗?

按照往年的惯例&#xff0c;春节后复工的 3 月、4 月是人员跳槽最频繁的时候&#xff0c;俗称“金三银四”。然而&#xff0c;市场大环境的影响&#xff0c;很多行业感受到了一丝寒冷的气息。 我们以为受影响比较轻的互联网行业&#xff0c;头上也充满乌云&#xff0c;所谓互联…

ROS2机器人编程简述humble-第四章-BASIC DETECTOR .3

书中程序适用于turtlebot、husky等多种机器人&#xff0c;配置相似都可以用的。支持ROS2版本foxy、humble。基础检测效果如下&#xff1a;由于缺&#xffe5;&#xff0c;所有设备都非常老旧&#xff0c;都是其他实验室淘汰或者拼凑出来的设备。机器人控制笔记本是2010年版本。…

九龙证券|本周5只新股申购,特斯拉、蔚来、理想的供应商来A股了!

据现在组织&#xff0c;2月13日到17日共有5只新股申购&#xff0c;其间上证主板2只&#xff0c;深证主板1只&#xff0c;北交所2只。 2月14日发动打新的深证主板新股多利科技成立于2010年&#xff0c;是一家专心于轿车冲压零部件及相关模具的开发、出产与出售的企业。从2020年…

nodejs版本管理器nvm下载,安装详情

文章目录前言一、NVM下载二、NVM安装三.使用NVM安装nodejs1.NVM常用命令2.安装node3.使用node前言 安装nodejs方式有两种。 第一种&#xff1a;官网下载  通过nodejs官网https://nodejs.org/zh-cn/下载安装 &#xff0c;但有个缺陷&#xff0c;不同版本的nodejs无法顺利的切…