分布式锁-Redis

news2024/12/23 18:05:08

一、为什么要有锁的概念

1.假如现在我们有这么一个场景:

用户在淘宝app上购买商品,用户提交订单的时候提交了,多点击了几次。

不管用户点击几次,只要用户一直停留在一个页面,那么就必须生成一个订单。

1.1 如果我们的服务是单体服务的话

在这里插入图片描述

比如现在我们的并发量,单体服务不够我们来支撑了,那么我们得升级到微服务,这个时候我们的大致流程图就变成这样了。

1.2 微服务的话

在这里插入图片描述

二、分布式锁的实现方式

1.Redis 实现分布式锁

1.2 SETNX命令

使用Redis完成分布式锁之前,首先我们的Redis必须有 互斥 的能力,我们可以使用 SETNX 命令,这个命令表示 SET IF NOT EXISTS,
如果key不存在,我们就设置他的值。如果存在我们什么也不做。

我们使用Redis客户端来演示一下:

  1. 客户端1加锁:
    在这里插入图片描述
    加锁成功返回的是1
    2.客户端2加锁:
    在这里插入图片描述
    返回的是0,代表我们加锁失败了
    此时,加锁成功的客户端,就可以去操作我们的数据库了,可以提交订单添加数据了。操作完成后。我们执行Redis的 Del命令删除掉这个Key,也就是我们释放锁资源。
    3.释放锁资源
    在这里插入图片描述

其中这样会导致一些问题。不知道大家想到了没有
假如说我们的客户端1拿到锁资源后,如果发生了下面的场景,就会导致这个锁资源一直被它占用着

  • 程序处理业务逻辑异常,没及时释放锁资源
  • 进程挂了,没机会事放锁

这就导致 死锁
其实解决这个问题很简单,我们看下Redis里面的这个命令。

1.3 SETEX命令

在Redis种实现时,就是我们添加这个KEY的时候 我们给他加一个过期时间。
假设我们这里操作共享资源的时间不超过10s,那我们加锁时这么添加
在这里插入图片描述
这样一来,无论客户端是否异常,这个锁都可以在10s后被【自动释放】,其他客户端就可以拿到锁资源。

但是现在还是有问题:
现在的操作,加锁、设置过期时2条命令,有没有可能只执行了第一条命令,第二条却 来不及 执行呢?例如

  • SETNX 执行成功,执行EXPIRE时由于忘了问题,执行失败
  • SETNX 执行成功,Redis异常宕机,EXPIRE没有计汇执行
  • SETNX 执行成功,客户端异常崩溃,EXPIRE也没有机会执行

总之,这两条命令不能保证时原子性操作(一起成功),就有潜在的风险导致过期时间设置失败,依旧发生 死锁 的问题

在Redis 2.6.12之后,Redis扩展了SET命令的参数,我们使用这个命令就可以了。

SET lock 1 EX 10 NX

但是这样的话,其实我们这还要一个问题。比如
我A加了锁,我在执行我的逻辑
然后B线程来了,给我把我的锁给我干掉了 这样不就乱套了么

因为我们上面写 删除锁的时候,我们并没有去判断这个锁,是不是我们线程A的锁,所以就会发生释放别人锁的风险,这样的解决流程,非常不 严谨,那么如何解决这个问题呢?

1.4 释放了别人的锁怎么办?

其实我们可以这样写,比如我们添加锁的时候设置
set 锁资源名称 当前线程的唯一标识 EX 过期时间 NX

SET lock $uuid EX 20 NX

然后我们释放锁资源的时候,判断当前key的value是不是我们当前线程所持有的,如果是就进行删除,那我们的命令是不是可以这么写

//如果get到的lock的值 == 我们的线程持有的值
if redis.get("lock") == $uuid:
	//那么我们就进行删除操作
    redis.del("lock")

这里释放锁使用的是 GET+DEL 两条命令,这时,又会遇到我们前面讲的原子性,那么怎么解决呢?

1.5 使用Lua脚本来保证原子性和解锁?

我们可以使用Lua脚结合get和del一起保证原子性

//安全释放锁的Lua脚本如下:
if redis.call("GET",KEYS[1]) == ARGV[1]
then
    return redis.call("DEL",KEYS[1])
else
    return 0
end

1.6 使用Jedis 实现分布式锁

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Arrays;

@Component
public class RedisLock {

    @Autowired
    private JedisPool jedisPool;

    private final static String RELEASE_LOCK_LUA =
            "if redis.call('get',KEYS[1])==ARGV[1] then\n" +
                    "        return redis.call('del', KEYS[1])\n" +
                    "    else return 0 end";


    /**
     * 加锁代码
     * @param key     key值
     * @param timeout  过期时间
     * @param value   value值 
     * @return   true成功 false失败
     */
    public boolean lock(String key, Long timeout, String value) {
        Jedis jedis = null;
        try {

            SetParams params = new SetParams();
            params.px(timeout);
            params.nx();
            if ("OK".equals(jedis.set(key, value, params)) {
                return true;
            }else{
                return false;
            }
        } catch (Exception e) {
            throw new RuntimeException("分布式锁尝试加锁失败!");
        } finally {
            jedis.close();
        }
    }

    /**
     * 释放所资源
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long result = (Long) jedis.eval(RELEASE_LOCK_LUA,
                    Arrays.asList(key),
                    Arrays.asList(value));
            if (result.longValue() != 0L) {
                System.out.println("Redis上的锁已释放!");
            } else {
                System.out.println("Redis上的锁释放失败!");
            }
        } catch (Exception e) {
            throw new RuntimeException("释放锁失败!", e);
        } finally {
            if (jedis != null) jedis.close();
            System.out.println("本地锁所有权已释放!");
        }
    }

}

1.7 使用Redission实现分布式锁

redission中两个加锁的方法

    /**
     * 该方法尝试在指定的等待时间内获取锁,并且设置锁的租期
     * <p>
     * 如果锁当前不可用,方法会等待一段时间(waitTime)直到获取到锁,或者在等待期间线程被中断抛出 InterruptedException
     * <p>
     * 如果在等待期间获取到锁,则返回 true
     * <p>
     * 如果在等待期间未能获取到锁(超过等待时间),则返回 false。
     * <p>
     * 获取到锁后,锁会自动在指定的租期时间(leaseTime)后释放。
     *
     * @param waitTime  参数指定了尝试获取锁的最大等待时间
     * @param leaseTime 参数指定了锁的租期时间
     * @param unit      参数指定了 waitTime 和 leaseTime 的时间单位
     * @return
     * @throws InterruptedException
     */
    boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;


    /**
     * 该方法用于获取锁,并指定锁的租期时间
     * <p>
     * 调用该方法后,如果锁当前不可用,则当前线程将被阻塞,直到成功获取到锁。
     * <p>
     * 当调用 lock 方法时,如果锁当前不可用,则当前线程将一直等待,直到获取到锁。
     * <p>
     * 获取到锁后,锁将在指定的租期时间(leaseTime)后自动释放。
     * <p>
     * 如果将 leaseTime 参数设置为 -1,表示持有锁直到显式调用 unlock 方法来释放锁。
     * <p>
     * 使用 lock 方法可以确保当前线程获取到锁后,其他线程在此期间将被阻塞。该方法适用于需要确保获取到锁才能继续执行的场景,以及需要指定锁的租期时间的场景
     *
     * @param leaseTime 参数指定了锁的租期时间
     * @param unit      参数指定了 leaseTime 的时间单位。
     */
    void lock(long leaseTime, TimeUnit unit);

释放锁的方法都是

    /**
     * 该方法用于释放锁
     * <p>
     * 调用 unlock 方法将释放之前获取的锁,使其变为可用状态。
     * <p>
     * 只有持有锁的线程才能成功调用 unlock 方法释放锁。
     * <p>
     * unlock 方法没有返回值,它只是用于显式释放锁。
     * <p>
     * unlock 方法在 Redission 中用于释放之前获取的锁资源,以便其他线程能够获取到该锁并执行相应的任务。
     */
    void unlock();

1.8 Redis集群模式

Redis集群模式(主从复制的架构下) 也可以使用我们上面的这种方式来用

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

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

相关文章

单片机中GPIO八种工作模式详细分析

今天给大家讲解一下 GPIO 基础&#xff0c;参考资料&#xff1a; STM32F1xx 官方资料&#xff1a; 《STM32中文参考手册V10》-第8章通用和复用功能IO(GPIO和AFIO) GPIO 是通用输入/输出端口的简称&#xff0c;是 STM32 可控制的引脚。GPIO 的引脚与外部硬件设备连接&#xff…

2023护网蓝初面试题汇总

一、描述外网打点的流程&#xff1f; 二、举几个 FOFA 在外网打点过程中的使用小技巧&#xff1f; 三、如何识别 CND &#xff1f; 四、邮件钓鱼的准备工作有哪些&#xff1f; 五、判断出靶标的 CMS &#xff0c;对外网打点有什么意义&#xff1f; 六、 Apache Log4j2…

云原生之深入解析Kubernetes常见的多集群方案

一、前言 Kubernetes 从 1.8 版本起就声称单集群最多可支持 5000 个节点和 15 万个 Pod&#xff0c;实际上应该很少有公司会部署如此庞大的一个单集群&#xff0c;很多情况下因为各种各样的原因我们可能会部署多个集群&#xff0c;但是又想将它们统一起来管理&#xff0c;这时…

QT圆形进度条(QT桌面项目光照强度检测)

文章目录 前言一、编程思路二、核心代码实现总结 前言 本篇文章我们讲解QT实现圆形进度条&#xff0c;并实现动态的效果。 一、编程思路 实现QT圆形进度条其实是非常简单的&#xff0c;思路就是画两个圆弧。 这里大家就会觉得很奇怪了为什么画两个圆弧就能实现圆形进度条了呢…

极智AI | AIGC时代中AI巨头之间的博弈

欢迎关注我的公众号 [极智视界],获取我的更多经验分享 大家好,我是极智视界,本文来谈谈 AIGC时代AI巨头之间的博弈。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码下载,链接:https://t.zsxq.com/0aiNxERDq 下图是开复老师在 《AI 未来》演讲中一页…

举办各种活动可以选云仓酒庄的雷盛红酒205

初次品尝红酒的人&#xff0c;可能跟初次品茶一样&#xff0c;这种酸涩的味道为什么会吸引这么多人魂牵梦绕。就像初生的婴儿&#xff0c;只有啼哭着面对世界&#xff0c;不解的望着新世界&#xff0c;只有时间的沉淀&#xff0c;实践的积累才会慢慢懂得&#xff0c;品酒也是这…

[Nacos] Nacos Server处理注册请求 (六)

文章目录 InstanceController.register()1.获取到请求中指定属性的值2.通过请求参数组装出instance3.将instance写入到注册表3.1 创建一个空的service临时的实例3.1.1 重写计算校验和3.1.2 将service写入到注册表3.1.2.1 将service写入注册表3.1.2.2 初始化service内部健康检测…

软考数据库详细知识点整理(全)

目录 第一章 计算机系统基本知识 1.1 计算机系统 1.1.1 计算机硬件组成 1.1.2 中央处理单元 1.1.3 数据表示 1.1.4 校验码 1.2 计算机体系结构 1.2.1 体系结构分类 1.2.2 指令系统存 1.2.3 储系系统 1.2.4 输入/输出技术 1.2.5 总线结构 1.3 可靠性、性能、安全 …

MySQL数据库---笔记3

MySQL数据库---笔记3 一、储存引擎1.1、MySQL体系结构1.2、存储引擎简介1.3、存储引擎特点1.3、存储引擎选择 二、索引 一、储存引擎 1.1、MySQL体系结构 MySQL体系结构图 连接层 最上层是一些客户端和链接服务&#xff0c;主要完成一些类似于连接处理、授权认证、及相关的安…

MySQL索引优化实战EXPLAIN解析

先来介绍一下具体的业务场景 当用户登录后&#xff0c;需要查看能够学习的课程&#xff0c;不同的用户看到的课程是不同的&#xff0c;课程存在权限&#xff0c;权限是被下面lesson_user_permissions表控制的&#xff0c;其中sys_user_id 和 lesson_id 作为联合主键 另外还有一…

Prompt Engineering | 对话聊天prompt

&#x1f604; 使用LLM来搭建一个定制的聊天机器人&#xff0c;只需要很少的工作量。 ⭐ 本文将讲解如何利用聊天格式与个性化或专门针对特兹那个任务或行为的聊天机器人进行多伦对话。 文章目录 1、提供对话的早期部分&#xff0c;引导模型继续聊天2、示例&#xff1a;构建一个…

基于ZeroTier虚拟网络搭建分流策略及创建IPv6网络

假设无数个成员&#xff08;移动设备、终端、Docker&#xff09;&#xff0c;需要劫持所有请求&#xff0c;并根据它们请求所访问的域名解析出IPv6或者IPv4地址来进行有效的选择性访问 说的直白一点点就是&#xff1a;分流策略 这里例一下需要做的工作&#xff0c;来看一下&am…

多维时序预测 | Matlab基于最小二乘支持向量机LSSVM多维时间序列预测,LSSVM多变量时间序列预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 基于最小二乘支持向量机LSSVM多维时间序列预测LSSVM多变量时间序列预测,matlab代码 评价指标包括:MAPE、MAE、RMSE和R2等,代码质量极高,

【毕业季】青春散场,我们期待下一场的开幕

青春&#xff0c;是一段追梦的旅程&#xff0c;毕业&#xff0c;是一次释放梦想的契机&#xff0c;祝愿每位毕业生在新的征程中&#xff0c;勇往直前&#xff0c;追逐梦想。 目录 青春散场&#xff0c;我们期待下一场的开幕 回忆过去 憧憬未来 青春散场&#xff0c;我们期待…

做好功能测试需要的8项基本技能【点工进来】

功能测试是测试工程师的基础功&#xff0c;很多人功能测试还做不好&#xff0c;就想去做性能测试、自动化测试。很多人对功能测试的理解就是点点点&#xff0c;如何自己不用心去悟&#xff0c;去研究&#xff0c;那么你的职业生涯也就停留在点点点上了。在这里&#xff0c;我把…

重磅新书上市,带你看看了不起的芯片!

千呼万唤始出来&#xff0c;我的第一本书《了不起的芯片》今天正式和大家见面啦! 任何一本书的背后都有一段不为人知的曲折故事&#xff0c;在此和大家分享一下我写这本书的心路历程。希望我的经历对你能有一些帮助&#xff0c;也希望你能喜欢我的作品。我还为大家申请了专属优…

vite3+vue3 项目打包优化三 — CDN加速、文件压缩

1. CDN在线加速 内容分发网络&#xff08;Content Delivery Network&#xff0c;简称 CDN&#xff09;&#xff0c;是构建在数据网络上的一种分布式内容分发网&#xff0c;它可以让用户从最近的服务器请求资源&#xff0c;以提升网络请求的响应速度。 通常情况下&#xff0c;…

tcp套接字的应用

tcp服务端流程 tcp客户端流程 客户端代码 tcpClient.hpp #include<iostream> #include<string> #include<cstring> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in…

DG4pros高楼区地籍建模项目报告

引言 据睿铂统计&#xff0c;目前约70%倾斜摄影相机都用于测量项目&#xff0c;其中绝大部分是地籍测量相关项目。例如黑龙江某客户已使用睿铂相机累计完成约1000平方公里的地籍项目。倾斜摄影技术虽然在农村地籍测量项目中应用较好&#xff0c;但却无法解决高楼区域地籍测量的…

使用 SD-WAN 实现企业级 WAN 敏捷性、简易性和性能

VMware SD-WAN 提高了敏捷性和成本效益&#xff0c;同时确保跨 WAN 的应用性能。 当今的分支机构用户正在使用更多的广域网 (WAN) 带宽&#xff0c;因为他们需要通过 Zoom、WebEx、Microsoft 365 等工具进行在线协作&#xff0c;更多地用到 “软件即服务”(SaaS) 和云计算服务…