Redis分布式锁最牛逼的实现(Java 版,最牛逼的实现方式)

news2024/11/19 2:36:09

写在前面的话

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。

本篇博客将介绍第二种方式,基于Redis实现分布式锁。

为什么需要分布式锁?

在单机环境下编写多线程程序时,为了避免多个线程同时操作同一个资源,我们往往会通过加锁来实现互斥,以保证同一时间只有一个操作者对某个资源执行操作,在单机多进程的情况下,如果们想操作同一个共享资源,我们也可以通过操作系统提供的文件锁和心好凉来实现互斥,这些都是单台机器上的操作。

而在分布式环境下,如果不同机器上的不同进程需要同时操作某一个共享资源,我们同样也需要这样一个统一的锁来实现互斥。这个时候,我们就需要一个平台来提供这样一个互斥的能力,通常我们会采用一些能够提供一致性的服务,比如 ZooKeeper、 etcd 来满足对一致性要求较高的场景下的互斥需求,当然,也有些服务会用数据库,比如 MySQL,来实现互斥,然而在某些高并发业务场景下,我们通常会采用 Redis来实现。

Redis分布式锁的实现方式

Redis分布式锁主要有以下几种实现方式:

1. SETNX命令

SETNX命令可以实现在键不存在的情况下设置键的值,利用这一特性可以实现分布式锁的功能。代码如下:

SETNX lock_key 1

上述命令会尝试将键名为lock_key的键的值设置为1,只有当该键不存在时,才会进行设置,并返回1;如果该键已存在,则不进行设置,并返回0。利用这个特性,可以实现分布式锁的加锁操作,代码如下:

boolean lock = redis.setnx("lock_key", "1");
if (lock) {
    // 获取锁成功
    // ...
} else {
    // 获取锁失败,需要重试
    // ...
}

解锁操作可以通过DEL命令删除锁对应的键来实现,代码如下:

redis.del("lock_key");

2. SET命令带过期时间

SET命令可以设置键的值以及过期时间。利用这一特性,可以实现分布式锁的自动释放。代码如下:

SET lock_key 1 PX 30000

上述命令会将键名为lock_key的键的值设置为1,并设置该键30秒后自动过期。利用这个特性,可以实现分布式锁的加锁操作,代码如下:

boolean lock = redis.set("lock_key", "1", "PX", 30000, "NX");
if (lock != null && lock.equalsIgnoreCase("OK")) {
    // 获取锁成功
    // ...
} else {
    // 获取锁失败,需要重试
    // ...
}

解锁操作可以通过DEL命令删除锁对应的键来实现。

3. Redlock算法

Redlock算法是Redis官方推荐的分布式锁算法,它使用多个独立的Redis实例来实现分布式锁。具体实现可参考以下步骤:

  1. 客户端获取当前时间戳作为请求的开始时间;
  2. 客户端依次尝试在多个独立的Redis实例上加锁,每个实例都需要设定相同的过期时间和相同的随机字符串(nonce);
  3. 客户端计算加锁操作的总时间,并将其与指定的超时时间进行比较,如果总时间小于超时时间,则认为加锁成功。否则,客户端需要在所有实例上尝试解锁操作。

Redlock算法的实现相对复杂,下文单独展开说明。

Redis分布式锁的底层原理

Redis分布式锁主要依赖于Redis的单线程模型和原子性操作特性。

Redis是一个单线程模型,它通过队列来实现多个客户端的请求排队执行。这意味着,在Redis中执行的每个命令都是原子性的,不会存在线程安全问题。

Redis提供了多个命令可以实现原子性的操作,如SETNX、GETSET等,它们都是通过Redis的事务机制以及WATCH命令来实现的。在Redis执行这些命令时,会对这些命令进行加锁,确保它们的原子性。

代码实践

下面通过Java代码演示如何使用Redis实现分布式锁。我们可以使用Jedis客户端来连接Redis服务器并进行操作。

// 初始化Redis客户端
Jedis redis = new Jedis("localhost", 6379);

// 加锁操作
boolean lock = redis.set("lock_key", "1", "PX", 30000, "NX");

if (lock != null && lock.equalsIgnoreCase("OK")) {
    // 获取锁成功
    // ...
} else {
    // 获取锁失败,需要重试
    // ...
}

// 解锁操作
redis.del("lock_key");

上述代码使用SET命令在Redis上加锁,并设置锁的过期时间为30秒。解锁操作通过DEL命令删除对应的键来实现。

在使用Redis分布式锁时,需要注意加锁、解锁的顺序以及超时时间的设置,以避免出现死锁等问题。

集群环境下Redis分布式锁的实现方式

集群环境下,Redis分布式锁的实现方式主要有以下两种:

  1. 基于Redlock算法的实现方式

在集群环境下,为了保证分布式锁的可靠性和正确性,可以采用Redlock算法来实现。该算法主要包括以下步骤:

  • 客户端获取当前时间戳作为请求的开始时间;
  • 客户端尝试在多个独立的Redis节点上加锁,每个节点都需要设置相同的过期时间和随机字符串(nonce);
  • 如果在大部分Redis节点上加锁成功,并且在指定的超时时间内完成了加锁操作,则认为加锁成功。否则,客户端需要在所有节点上尝试解锁操作。

通过这种方式,可以有效地避免因某一个节点失效导致的分布式锁失效问题。但需要注意的是,使用Redlock算法的开销比其他方式要高,并且并不是绝对的可靠,因此需要根据具体场景进行选择。下文单独示例说明。

  1. 基于Lua脚本的实现方式

除了Redlock算法,还可以使用基于Lua脚本的方式来实现Redis分布式锁。该方式的主要思路是通过执行一段Lua脚本来保证加锁和解锁的原子性,避免了由于网络延迟等因素导致的加锁和解锁不一致的问题。

以下是基于Lua脚本实现Redis分布式锁的代码示例:

public class RedisLock {
    private Jedis jedis;
    private String lockKey;
    private String requestId;
    private int expireTime;

    public RedisLock(Jedis jedis, String lockKey, int expireTime) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.expireTime = expireTime;
        this.requestId = UUID.randomUUID().toString();
    }

    /**
     * 尝试获取锁
     */
    public boolean tryLock() {
        String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
        if ("OK".equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 释放锁
     */
    public void unlock() {
        // 使用Lua脚本确保原子性
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    }
}

在该示例中,使用tryLock()方法来尝试加锁,并使用unlock()方法来释放锁。其中,加锁的实现方式为执行SET命令,同时设置NX(只在键不存在时设置)和PX(设置过期时间)选项,并将请求ID作为值存入Redis中。解锁采用了使用Lua脚本执行DEL命令的方式,以保证加锁和解锁的原子性。

通过这种方式,我们可以简单地实现Redis分布式锁的使用,并且在多个节点之间也可以正确地工作。

Redlock 实现的分布式锁以及对应的代码实现细节

Redlock是一种分布式锁算法,由Redis官方推出,并用于解决在分布式系统中实现分布式锁的问题。Redlock算法采用多个节点之间互斥的方式获取分布式锁,可以保证在大部分节点正常情况下分布式锁的可靠性,并允许在某些更繁忙或网络质量较差的节点上失败,从而确保分布式锁的稳定性。

下面是Redlock的JAVA代码实现细节:

首先,定义一个名为RedisLock的JAVA类,该类实现了Lock接口并包含了以下几个属性:

private JedisPool[] jedisPools; // Redis连接池
private int quorum; // 在多少个节点上加锁或解锁成功
private int retryCount; // 重试次数
private int retryDelay; // 每次重试之间的延迟时间

接着,实现加锁方法lock():

@Override
public void lock() {
    String nonce = generateNonce();
    for (int i = 0; i < retryCount; i++) {
        int count = 0;
        long start = System.currentTimeMillis();
        for (JedisPool pool : jedisPools) {
            try (Jedis jedis = pool.getResource()) {
                String result = jedis.set(lockKey, nonce, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
                if (LOCK_SUCCESS.equals(result)) {
                    count++;
                }
            } catch (Exception e) {
                // ignore
            }
        }
        if (count >= quorum && System.currentTimeMillis() - start <= expireTime) {
            return;
        }
        unlock(nonce);
        try {
            Thread.sleep(retryDelay);
        } catch (InterruptedException e) {
            // ignore
        }
    }
    throw new LockException("Unable to acquire lock.");
}

在加锁方法中,我们首先生成了一个随机字符串nonce作为锁的值,并在每个Redis实例上进行原子性的set操作,返回成功加锁的实例数。如果获取到锁的实例数大于等于quorum(即多数节点),并且加锁操作完成的时间小于锁的过期时间expireTime,则表示加锁成功,否则认为加锁失败,触发重试机制。

实现解锁方法unlock()

private void unlock(String nonce) {
    for (JedisPool pool : jedisPools) {
        try (Jedis jedis = pool.getResource()) {
            String result = jedis.get(lockKey);
            if (nonce.equals(result)) {
                jedis.del(lockKey);
            }
        } catch (Exception e) {
            // ignore
        }
    }
}

在解锁方法中,我们遍历所有Redis实例,查询锁的值是否为当前nonce,如果是,则删除该实例上的锁。

综上所述,Redlock算法的JAVA代码实现主要包括两个方法:加锁方法和解锁方法。在加锁方法中,我们通过多次尝试,在大部分节点上获取到锁时完成加锁操作,并在获取到锁的多数节点上进行解锁操作。同时,我们还可以通过调整retryCount和retryDelay的参数来控制重试机制的次数和间隔。需要注意的是,Redlock算法对应的JAVA实现需要保证多个线程使用同一个Lock对象。

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

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

相关文章

WAF绕过-信息收集之反爬虫延时代理池 46

老师用的阿里云的服务器&#xff0c;装了宝塔和安全狗&#xff0c; 演示案例 Safedog-默认拦截机制分析绕过-未开CC 没有打开防止流量攻击的安全狗&#xff0c; 而这里&#xff0c;get请求可以直接看到返回结果&#xff0c;而head就不行。 我们就给工具换成get请求 在没有c…

JS数组方法map 和 forEach 的区别

一、定义&#xff1a; forEach(): 针对每一个元素执行提供的函数。 map(): 创建一个新的数组&#xff0c;其中每一个元素由调用数组中的每一个元素执行提供的函数得来。 二、区别 1、map 方法返回一个新的数组&#xff0c;而 forEach 方法不会返回任何值&#xff0c;仅仅是遍…

常见的作物模型有哪些?DSSAT模型、APSIM模型、WOFOST模型与PCSE模型等应用

目录 ①最新DSSAT作物模型建模方法及应用 ②基于Python语言快速批量运行DSSAT模型及交叉融合、扩展应用 ③R语言与作物模型&#xff08;以DSSAT模型为例&#xff09;融合应用 ④WOFOST模型与PCSE模型应用 ⑤基于R语言APSIM模型进阶应用与参数优化、批量模拟 ⑥遥感数据与…

十二、【修复工具组】

文章目录 污点修复画笔工具移除工具修复画笔工具修补工具内容感知移动工具红眼工具 污点修复画笔工具 提前说一下&#xff0c;如果光标是十字型的话&#xff0c;可以按一下大写键位&#xff0c;然后光标会变成画笔的圆形状态 污点修复画笔工具是根据画笔选区周围的颜色来进行…

品牌投放小红书种草笔记没有流量怎么办?

品牌做小红书种草投放&#xff0c;流量越大&#xff0c;种草笔记的转化就会越高&#xff0c;所以当你的小红书种草笔记没有流量的时候就要好好思索下是怎么回事&#xff0c;接下来伯乐网络传媒就来给大家分析问题&#xff0c;并提出针对性的解决方案。纯干货&#xff0c;建议收…

maven 编译.../maven-metadata.xml 报错

文章目录 问题解决 问题 突然编译报错: 解决 打开maven的里离线工作模式,感觉就是下载包到本地. 一个是在maven设置里面 或者直接在maven编译的窗口:

代购商城网站的建设与推广

随着互联网的普及和电子商务的快速发展&#xff0c;代购商城网站的建设与推广逐渐成为了商业发展的重要手段。代购商城&#xff0c;顾名思义&#xff0c;就是通过网站为消费者提供各种商品代购服务的平台。在这个平台上&#xff0c;消费者可以找到心仪的商品&#xff0c;并由专…

【记录贴】使用项目管理软件管理大型复杂项目是种什么体验?(二)

最近接手的一个中大型项目&#xff0c;前段时间才解决了需求管理的难题&#xff0c;在经历一个小长假后&#xff0c;又暴露出了一堆问题&#xff0c;那些节后再说的工作和节后再回复的邮件终于是避无可避了。。。 给大家简述下我遇到的几个比较典型的问题&#xff0c;看看你们有…

11-1-转置卷积ConvTransposed2d

文章目录 1. 卷积Conv2d2. 转置卷积ConvTransposed2d -- 使用方法3. 计算放大后的size参考:能实现实现上采样,用于 解码、图像超分辨 卷积&转置卷积的不同之处 Conv2d vs ConvTransposed2d 1. 卷积Conv2d Conv2d是valid卷积stride是卷积核的 滑动步长padding是图像四周…

12种发朋友圈黄金模板

随着朋友圈在社交媒体中的日渐重要&#xff0c;越来越多的企业开始将其作为一种有效的营销渠道。但是&#xff0c;在朋友圈中发布内容并不是一件容易的事情&#xff0c;如何创造有吸引力和互动性的内容&#xff0c;成为了所有运营人员所面临的问题。 小编将分享12种发朋友圈黄金…

Flink中KeyBy、分区、分组的正确理解

1.Flink中的KeyBy 在Flink中&#xff0c;KeyBy作为我们常用的一个聚合类型算子&#xff0c;它可以按照相同的Key对数据进行重新分区&#xff0c;分区之后分配到对应的子任务当中去。 源码解析 keyBy 得到的结果将不再是 DataStream&#xff0c;而是会将 DataStream 转换为 Key…

机器学习之过拟合与欠拟合,K折交叉验证详解【含代码】

欠拟合 欠拟合&#xff08;Underfitting&#xff09;是机器学习和统计学中的一个术语&#xff0c;描述了模型在训练数据和新数据&#xff08;如测试数据或验证数据&#xff09;上都表现不佳的情况。换句话说&#xff0c;欠拟合的模型没有足够地“学习”或“捕捉”数据中的模式…

力扣刷题 day42:10-12

1.最大正方形 在一个由 0 和 1 组成的二维矩阵内&#xff0c;找到只包含 1 的最大正方形&#xff0c;并返回其面积。 方法一&#xff1a;动态规划 #方法一&#xff1a;动态规划 def maximalSquar(matrix):dp[[0]*(len(matrix[0])1) for i in range(len(matrix)1)] #dp[i][j…

十三、【画笔工具组】

文章目录 画笔工具铅笔工具颜色替换工具混合器画笔工具 画笔工具跟混合器画笔工具&#xff0c;是我们平时使用频率较高的两款工具: 画笔工具 可以把画笔工具看成我们用的毛笔,使用时可以在拾色器里边选择我们需要的画笔颜色,可以把拾色器当做我们画画时用的一个颜料盘&#xf…

健效达海豚妈妈儿保项目推介会盛大启幕,聚焦互联网+精准医疗

2023年10月12日&#xff0c;由上海健启星科技发展有限公司和北京安智因生物技术有限公司联合主办的“2023互联网精准医学平台助力基层医疗|海豚妈妈儿保项目推介会”在中国苏州盛大启幕。 本次项目推介会得到国内行业专家、权威学者、国内知名三甲名医教授、头部企业、学术大咖…

grafana接入OpenTSDB设置大盘语法

目录 1、filter过滤语法1.1 精准匹配1.2 正则匹配1.3 通配符匹配 完整示例1、 展示应用app的CPU利用率监控2&#xff09;展示应用app的在线核数 1、filter过滤语法 1.1 精准匹配 literal_or &#xff1a; tagv的过滤规则: 精确匹配多项迭代值&#xff0c;多项迭代值以’|分隔&a…

【面试经典150 | 哈希表】两数之和

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;暴力枚举方法二&#xff1a;哈希表 写在最后 Tag 【哈希表】【数组】 题目来源 1. 两数之和 题目解读 给定一个下标从 1 开始按照 非递减顺序排列 的整数数组 numbers&#xff0c;找出两数之和等于 target 的两个数&…

软件测试学习(五)

报告发现的问题 设法修复软件缺陷 ●没有足够的时间。在任何一个项目中&#xff0c;通常是软件功能太多&#xff0c;而代码编写人员和软件测试人员太少&#xff0c;而且进度中没有留出足够的空间来完成项目。假如你正在制作税务处理程序&#xff0c;4月15日 (赶在应付税务检查…

计算机毕业设计选什么题目好?springboot 高校学生综合测评管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

枚举探秘:Java中的神奇力量!

在 Java 枚举出现之前&#xff0c;通常会使用常量类来表示一组固定的常量值&#xff0c;直到Java 1.5之后推出了枚举&#xff0c;那么枚举类型有哪些特点&#xff0c;它比常量类又好在哪里呢。 本文将分析一下枚举的特点及用法。 一、什么是枚举 Java 枚举&#xff08;Enum&a…