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

news2024/12/25 0:27:11

目录

  • 一、分布式锁实现原理
  • 二、不同的分布式锁实现方案
  • 三、Redis 的 setnx 实现互斥锁
  • 四、基于 Redis 实现分布式锁初级版
  • 五、误删锁问题(业务阻塞导致)
  • 六、误删锁(Redis 命令原子性导致)
    • (1) Lua 脚本
    • (2) Redis 编写和执行 Lua 脚本
    • (3) 复杂逻辑的 Lua 脚本(业务相关)
    • (4) RedisTemplate 执行 Lua 脚本

一、分布式锁实现原理

在这里插入图片描述

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

在这里插入图片描述

二、不同的分布式锁实现方案

🎄 分布式锁的核心是实现多进程之间锁的互斥,而满足这一点的方式有很多,常见的有三种:
在这里插入图片描述

三、Redis 的 setnx 实现互斥锁

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

在这里插入图片描述

🎄锁获取了,还没有来得及设置过期时间服务器就宕机了
🎄保证 setnx(获取锁)和 expire 设置过期时间两个操作是原子性的

在这里插入图片描述

在这里插入图片描述

四、基于 Redis 实现分布式锁初级版

🎄 需求:定义一个类,实现下面的接口,利用 Redis 实现分布式锁功能

public interface LockInter {
    /**
     * 尝试获取锁
     *
     * @param ttlSecond 锁的过期时间
     * @return true: 成功获取锁; false: 获取锁失败
     */
    boolean tryLock(long ttlSecond);

    /**
     * 释放锁
     */
    void unlock();
}
public class LockImplV1 implements LockInter {
    private String name; // 和业务相关的锁的名字
    private StringRedisTemplate stringRedisTemplate;

    private static final String LOCK_KEY_PREFIX = "lock:";

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

    @Override
    public boolean tryLock(long ttlSecond) {
        String key = LOCK_KEY_PREFIX + name;
        // value 里面放当前线程的唯一标识(线程 ID)
        String val = Thread.currentThread().getId() + "";
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key,
                val,
                ttlSecond,
                TimeUnit.SECONDS);

        // Boolean -- boolean 会自动拆箱
        // 当 success 为 null 的时候会抛异常
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        stringRedisTemplate.delete(LOCK_KEY_PREFIX + name);
    }
}

在这里插入图片描述

在这里插入图片描述

五、误删锁问题(业务阻塞导致)

在这里插入图片描述

在这里插入图片描述


需求:修改之前的分布式锁实现,满足:

  • 在获取锁时存入线程标识(可以用 UUID 表示)
  • 在释放锁时先获取锁中的线程标识,判断是否与当前线程标识一致
    ① 如果一致则释放锁
    ② 如果不一致则不释放锁

要用 UUID,避免线程 ID 重复

public class LockImplV2 implements LockInter {
    private String name; // 和业务相关的锁的名字
    private StringRedisTemplate stringRedisTemplate;

    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final String UNIQUE_PREFIX = UUID.randomUUID().toString(true);

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

    @Override
    public boolean tryLock(long ttlSecond) {
        String key = LOCK_KEY_PREFIX + name;
        // value 里面放当前线程的唯一标识(线程 ID)
        String val = UNIQUE_PREFIX + Thread.currentThread().getId();
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key,
                val,
                ttlSecond,
                TimeUnit.SECONDS);

        // Boolean -- boolean 会自动拆箱
        // 当 success 为 null 的时候会抛异常
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        String k = LOCK_KEY_PREFIX + name;
        String cacheVal = stringRedisTemplate.opsForValue().get(k);

        String curVal = UNIQUE_PREFIX + Thread.currentThread().getId();

        if (curVal.equals(cacheVal)) {
            stringRedisTemplate.delete(k);
        }
    }
}

六、误删锁(Redis 命令原子性导致)

在这里插入图片描述

解决方案:Lua 脚本

(1) Lua 脚本

📖 Redis 提供了 Lua 脚本功能,在一个脚本中编写多条 Redis 命令,确保多条命令执行时的原子性
📖 Lua 是一种编程语言 https://www.runoob.com/lua/lua-tutorial.html


(2) Redis 编写和执行 Lua 脚本

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

参数有两种:key 类型参数,其他参数

📖 如果脚本中的 key(gender)、value(handsomeBoy)不想写死,可以作为参数传递
📖 key 类型参数会放入 KEYS 数组
📖 其它参数会放入 ARGV 数组,在脚本中可以从 KEYS 和 ARGV 数组获取这些参数

在这里插入图片描述

Lua 语言中下标从 1 开始

(3) 复杂逻辑的 Lua 脚本(业务相关)

在这里插入图片描述

📖 获取锁(Redis 缓存)中的线程标识 cacheVal
📖 判断是否与当前线程标识一致 curVal
📖 如果一致则释放锁(del
📖 如果不一致则什么都不做

上述操作要通过 Lua 脚本执行,保证多条 Redis 命令的原子性(防止误删锁)

--- 当前线程的线程标识
local curVal = ARGV[1] 

--- 要删除的锁的 key
local lockKey = KEYS[1]

if(cacheVal == curVal) 
	then
		return redis.call('DEL', KEYS[1])
	end
return 0

在这里插入图片描述

(4) RedisTemplate 执行 Lua 脚本

在这里插入图片描述

Lua 脚本可写在 Java 的类路径下的资源文件夹中
在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述

public class LockImplV3 implements LockInter {
    private String name; // 和业务相关的锁的名字
    private StringRedisTemplate stringRedisTemplate;

    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final String UNIQUE_PREFIX = UUID.randomUUID().toString(true);

    private static final DefaultRedisScript<Long> UNLOCK_LUA_SCRIPT;

    static { // 初始化 UNLOCK_LUA_SCRIPT
        UNLOCK_LUA_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_LUA_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_LUA_SCRIPT.setResultType(Long.class);
    }

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

    @Override
    public boolean tryLock(long ttlSecond) {
        String key = LOCK_KEY_PREFIX + name;
        // value 里面放当前线程的唯一标识(线程 ID)
        String val = UNIQUE_PREFIX + Thread.currentThread().getId();
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key,
                val,
                ttlSecond,
                TimeUnit.SECONDS);

        // Boolean -- boolean 会自动拆箱
        // 当 success 为 null 的时候会抛异常
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        stringRedisTemplate.execute(
                UNLOCK_LUA_SCRIPT,
                Collections.singletonList(LOCK_KEY_PREFIX + name),
                UNIQUE_PREFIX + Thread.currentThread().getId());
    }
}

在这里插入图片描述

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

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

相关文章

VSCode LSP 语言服务器协议总结

为什么使用语言服务器协议&#xff1f; LSP(Language Server Protocol)语言服务器是一种特殊的 Visual Studio Code 扩展&#xff0c;可为许多编程语言提供编辑体验。使用语言服务器&#xff0c;您可以实现自动完成、错误检查&#xff08;诊断&#xff09;、跳转到定义以及VS …

python绘制二维直方图

文章目录 histscatterhist2d histscatter 如果想描述二维数据的分布特征&#xff0c;那么一个直方图显然是不够用的&#xff0c;为此可使用两个直方图分别代表x和y方向上的分布情况&#xff0c;同时透过散点图查看其整体的分布特征。 下面创建一组二元高斯分布的数据&#xf…

Nature揭秘:足量提供这个营养素可激活免疫细胞对抗肿瘤

圣犹太儿童研究医院&#xff08;St. Jude Childrens Research Hospital&#xff09;的科学家们发现&#xff0c;免疫细胞和肿瘤细胞在它们的局部环境中争会夺谷氨酰胺。谷氨酰胺是一种营养物质&#xff0c;对抗癌活性具有重要意义。如果癌细胞垄断谷氨酰胺&#xff0c;则可以阻…

2023年7月广州/东莞/深圳传统行业产品经理NPDP认证招生

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

单例模式:懒汉式和饿汉式

目录 懒汉模式和饿汉模式 区别 示例 懒汉模式线程不安全 懒汉模式线程安全 懒汉模式内部静态变量线程安全 饿汉式线程安全 指的是在系统生命周期内&#xff0c;只产生一个实例。 懒汉模式和饿汉模式 分为懒汉式和饿汉式 区别 创建时机和线程安全 线程安全&#xff1…

密码学入门——单向散列函数

文章目录 参考书一、简介二、术语介绍三、散列函数的应用四、单向散列函数的具体例子 参考书 图解密码技术&#xff0c;第三版 一、简介 单向散列函数&#xff08;One-way Hash Function&#xff09;是一种将任意长度的输入数据映射为固定长度输出的函数。该函数通常被用于密…

asyncio.run() cannot be called from a running event loop

这个问题一般在jupyter中出现 搜到的解决方法也不怎么好用 知道看到了这个评论 很感谢 # pip install nest_asyncio首先安装此模块 import nest_asyncio#调用 nest_asyncio.apply()成功解决

如何整合spring cloud常用组件?

目录 一、SpringCloud Alibaba 简介 1、简介 2、为什么使用 3、版本选择 4、项目中的依赖 二、SpringCloud Alibaba-Nacos[作为注册中心] 1、下载 nacos-server 2、启动 nacos-server 3、将微服务注册到 nacos 中 三、SpringCloud Alibaba-Nacos[作为配置中心] 四、…

天猫精灵狄耐克联合研发新品, 携手打造智慧居家新体验

2023年6月28日&#xff0c;以“以Ai相伴&#xff0c;智慧赋能”为主题的2023厦门人工智能产业高峰论坛&#xff0c;在素有“中国软件特色名城”美誉的厦门隆重举行。 2023厦门人工智能产业高峰论坛现场 厦门狄耐克智能科技股份有限公司与阿里巴巴智能互联成为战略合作伙伴&…

智能抄表建设方案

城市基础设施改造是智慧城市升级建设的基础。如供水&#xff0c;水资源是人类生存发展和经济社会可持续发展的重要基础。然而&#xff0c;传统的人工抄表方式已经无法满足水资源管理和水务公司对高效、准确抄表管理的需求。由于人工抄表工作的不便捷性、耗时长和易出错性&#…

全方位对比 Postgres 和 MySQL (2023 版)

根据 2023 年 Stack Overflow 调研&#xff0c;Postgres 已经取代 MySQL 成为最受敬仰和渴望的数据库。 随着 Postgres 的发展势头愈发强劲&#xff0c;在 Postgres 和 MySQL 之间做选择变得更难了。 如果看安装数量&#xff0c;MySQL 可能仍是全球最大的开源数据库。 Postgre…

Jmeter接口关联(四)【使用正则表达式提取值】关联上下接口实际当中的运用

文章目录 前言一、Jmeter中正则表达式的运用&#xff08;使用案例讲解&#xff09;注意 前言 这篇主要是用一个实际的例子讲解正则表达式在2个有关联的接口中运用。 天气预报接口&#xff1a; 请求Key&#xff1a;79a35e2a5997710f3bdc86de81f21dbb 根据城市查询天气 接口地…

【missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun】

报错信息 安装xcode xcode-select --install 打开跳板效率会更高。

【Matlab】智能优化算法_人工蜂鸟算法AHA

【Matlab】智能优化算法_人工蜂鸟算法AHA 1.背景介绍2.数学模型2.1 初始化2.2 指导觅食2.3 领地觅食2.4 迁徙觅食 3.文件结构4.伪代码5.详细代码及注释5.1 AHA.m5.2 BenFunctions.m5.3 FunRange.m5.4 main.m5.5 SpaceBound.m 6.运行结果7.参考文献 1.背景介绍 蜂鸟是一种令人惊…

Java实现站内信

假如后台某个任务比较耗时&#xff0c;这时就需要任务完成时&#xff0c;通知一下用户&#xff0c;如下图&#xff0c;实现站内信的效果 两张表即可实现 t_message_content内容表 CREATE TABLE t_message_content (c_id int(11) NOT NULL AUTO_INCREMENT COMMENT 消息的id,se…

SpringCloud(4) Eureka 如何主动下线服务节点

目录 1.直接停掉客户端服务2.发送HTTP请求1&#xff09;调用DELETE接口2&#xff09;调用状态变更接口 3.客户端主动通知注册中心下线1&#xff09;代码示例2&#xff09;补充3&#xff09;测试 一共有三种从 Eureka 注册中心剔除服务的方式&#xff1a; 1.直接停掉客户端服务…

多个input相加计算结果为NaN的处理

为什么会出现NaN?&#xff08;复现一下&#xff09; NaN的出现&#xff1a;是因为input框绑定的都是同一个方法导致的。 因为我的需求&#xff1a;购买数量*优惠价(单价)-平台补贴-店铺补贴实付金额 的实时计算 原因&#xff1a;第一个input输入的时候&#xff0c;相应的其…

货币政策和汇率波动——使用Python进行量化分析

货币政策和汇率波动是国际贸易和投资中的重要问题&#xff0c;对于投资者来说具有重要的影响。本文将介绍如何使用Python进行量化分析&#xff0c;以揭示货币政策和汇率波动之间的关系。 一、货币政策与汇率波动 货币政策作为国家宏观调控的一种手段&#xff0c;对汇率波动具…

选读SQL经典实例笔记06_日期处理(上)

1. 计算一年有多少天 1.1. 方案 1.1.1. 找到当前年份的第一天 1.1.2. 加上1年以得到下一年的第一天 1.1.3. 得到的结果减去第一步得到的结果 1.2. DB2 1.2.1. sql select days((curr_year 1 year)) - days(curr_year)from (select (current_date -dayofyear(current_d…

数字电路设计——流水线处理器

数字电路设计——流水线处理器 从内存或是寄存器组读写、操作ALU是处理器中最耗时的操作。我们将一个指令拆成五个流水线阶段&#xff0c;每个阶段同时执行耗时的组合逻辑。这五个阶段分别是取指令、解码、执行、访存和写回。 在取指令阶段&#xff0c;处理器从指令存储器中读…