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

这篇具有很好参考价值的文章主要介绍了【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、分布式锁实现原理

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

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

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

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

🎄 分布式锁的核心是实现多进程之间锁的互斥,而满足这一点的方式有很多,常见的有三种:
【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性,Redis,redis,分布式,lua

三、Redis 的 setnx 实现互斥锁

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


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


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

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

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

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

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

四、基于 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);
    }
}

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

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

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

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

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


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

  • 在获取锁时存入线程标识(可以用 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 命令原子性导致)

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

解决方案:Lua 脚本

(1) Lua 脚本

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


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

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

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

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

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

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

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

Lua 语言中下标从 1 开始

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

【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性,Redis,redis,分布式,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

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

(4) RedisTemplate 执行 Lua 脚本

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

Lua 脚本可写在 Java 的类路径下的资源文件夹中
【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性,Redis,redis,分布式,lua

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


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

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

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

【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性,Redis,redis,分布式,lua文章来源地址https://www.toymoban.com/news/detail-554776.html

到了这里,关于【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 利用Java代码调用Lua脚本改造分布式锁

    4.8 利用Java代码调用Lua脚本改造分布式锁 lua脚本本身并不需要大家花费太多时间去研究,只需要知道如何调用,大致是什么意思即可,所以在笔记中并不会详细的去解释这些lua表达式的含义。 我们的RedisTemplate中,可以利用execute方法去执行lua脚本,参数对应关系就如下图股

    2024年04月10日
    浏览(36)
  • 【面试 分布式锁详细解析】续命 自旋锁 看门狗 重入锁,加锁 续命 解锁 核心源码,lua脚本解析,具体代码和lua脚本如何实现

    自己实现锁续命 在 controller 里开一个 线程 (可以为 守护线程) 每10秒,判断一个 这个 UUID是否存在,如果 存在,重置为 30秒。 如果不存在,守护线程 也结束。 基本的key value 基本的使用 setIfAbsent存在不设置 16384 Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。 R

    2023年04月09日
    浏览(31)
  • Redis实现分布式锁之----超时和失效(非原子性)问题----解决方案

    Redis实现分布式锁之----超时和失效(非原子性)问题----解决方案 超时和失效(非原子性)问题 原子性问题 :上锁时存入线程名称,删除时要先判断锁内的名称是不是自己的,是再删除,但是后面的判断 和删除非原子性 ,会有并发安全问题。 不可重入问题 :一个线程只能

    2024年02月07日
    浏览(32)
  • springboot整合redis+lua实现getdel操作保证原子性

    原始代码 脚本逻辑先获取redis的值,判断是否等于期望值。 条件成立则删除,不成立则返回0 测试代码 根据上面的逻辑加了测试, 在判断成功后等待5秒后执行删除操作。同时开启另外一个线程去修改这个key的值, 发现修改的线程一直阻塞。直到等待的线程5秒结束后且完成

    2024年02月05日
    浏览(34)
  • Lua脚本解决多条命令原子性问题

    Redis是一个流行的键值存储数据库,它提供了丰富的功能和命令。在Redis中,我们可以使用Lua脚本来编写多条命令,以确保这些命令的原子性执行。Lua是一种简单易学的编程语言,下面将介绍如何使用Redis提供的调用函数来操作Redis并保证原子性。 首先,让我们来了解一下Lua脚

    2024年02月14日
    浏览(29)
  • 分布式锁的原子性问题

    4.6 分布式锁的原子性问题 更为极端的误删逻辑说明: 线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删除锁,但是此时他的锁到期了,那么此时线程2进来,但

    2024年04月10日
    浏览(53)
  • 如何保证分布式情况下的幂等性

    关于这个分布式服务的幂等性,这是在使用分布式服务的时候会经常遇到的问题,比如,重复提交的问题。而幂等性,就是为了解决问题存在的一个概念了。 什么是幂等 幂等(idempotent、idempotence)是⼀个数学与计算机学概念,常⻅于抽象代数中。 在编程中⼀个幂等操作的特

    2024年02月07日
    浏览(39)
  • kafka 分布式的情况下,如何保证消息的顺序消费?

    目录 一、什么是分布式 二、kafka介绍 三、消息的顺序消费 四、如何保证消息的顺序消费   分布式是指将计算任务分散到多个计算节点上进行并行处理的一种计算模型。在分布式系统中,多台计算机通过网络互联,共同协作完成任务。每个计算节点都可以独立运行,并且可以

    2024年02月10日
    浏览(41)
  • 集群高并发环境下如何保证分布式唯一全局ID生成?

    在集群高并发环境下保证分布式唯一全局ID生成是一个具有挑战性的问题。下面笔者将为大家提供几种常见的解决方案: UUID是一个128位的全局唯一标识符,它可以在不同的计算机和时间上生成。UUID的生成是基于MAC地址、时间戳等信息,因此可以保证在分布式环境下的唯一性

    2024年02月13日
    浏览(36)
  • 分布式websocket即时通信(IM)系统保证消息可靠性【第八期】

    b站上面本期视频版本,观看视频食用更佳!点击即可跳转,找不到视频可以直接搜索我 目前叫 呆呆呆呆梦 目前已经写的文章有。并且有对应视频版本。 git项目地址 【IM即时通信系统(企聊聊)】点击可跳转 sprinboot单体项目升级成springcloud项目 【第一期】 前端项目技术选型

    2024年01月22日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包