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

写在前面的话

分布式锁一般有三种实现方式: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.mzph.cn/news/103631.shtml

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

相关文章

【使用 TensorFlow 2】03/3 创建自定义损失函数

一、说明 TensorFlow 2发布已经接近5年时间&#xff0c;不仅继承了Keras快速上手和易于使用的特性&#xff0c;同时还扩展了原有Keras所不支持的分布式训练的特性。3大设计原则&#xff1a;简化概念&#xff0c;海纳百川&#xff0c;构建生态.这是本系列的第三部分&#xff0c;…

Python数据挖掘项目实战——自动售货机销售数据分析

摘要&#xff1a;本案例将主要结合自动售货机的实际情况&#xff0c;对销售的历史数据进行处理&#xff0c;利用pyecharts库、Matplotlib库进行可视化分析&#xff0c;并对未来4周商品的销售额进行预测&#xff0c;从而为企业制定相应的自动售货机市场需求分析及销售建议提供参…

2、vscode c++ 项目配置调试及运行

文章目录 1、项目布局2、多项目管理2.1 先是一个总的CMakeLists.txt2.2 每个项目2.3 多版本OPENCV 3、调试和运行 接上一篇文章&#xff0c;vscode和cmake的c环境配置好以后&#xff0c;我们要写项目&#xff0c;再写对应的CMakeLists.txt 1、项目布局 . ├── bin ├── bu…

jmeter实现webservice接口测试

其实可以用jmeter两种sampler进行webservice的测试&#xff1a; 1、SOAP/XML-RPC Request(但是在jmeter3.2以后版本中已经取消了这个取样器) 2、HTTP请求 下面分别介绍两种方式 一、首先需要使用soupUI工具抓取webservice接口的部分需要的信息。 1、新建项目 2、新建成功的…

怎么修改linux的root@后面的名称

文章目录 场景.登录服务器,root后面的名称是随机的,想自定义名称建议,直接使用命令执行需要重启机子 场景.登录服务器,root后面的名称是随机的,想自定义名称 建议,直接使用命令执行 hostnamectl set-hostname rdd-test重新连接即可生效,实际也是修改了/etc/hostname名称 需要…

十三、前端开发知识快速入门

目录 一、HTML概述和基本结构1.1 概述1.2 基本结构1.3 html文档类型1.4 html注释 二、HTML常用标签2.1 块标签2.2 行内标签2.3 字符实体2.4 图片标签2.5 链接标签2.6 列表标签2.7 表单2.8 表格 三、页面布局四、CSS样式4.1 基本语法和页面引用4.2 文本样式设置4.3 颜色表示法4.…

中国陆地生态系统服务价值空间分布数据集

简介&#xff1a; 中国陆地生态系统服务价值空间分布产品是以全国陆地生态系统类型遥感分类为基础&#xff0c;生态系统类型包括&#xff1a;旱地、农田、针叶林、针阔混交林、阔叶林、灌木林、草原、灌草丛、草甸、湿地、荒漠、裸地、水系、冰川积雪、人工表面&#xff08;包…

B端产品需求分析的思路和方法 4大方面

需求分析对产品成功和客户满意度至关重要&#xff0c;它帮助团队深入了解用户需求&#xff0c;优化用户体验&#xff0c;减少开发中的需求变更&#xff0c;降低开发风险。如果缺乏产品分析&#xff0c;容易造成产品定位不准确&#xff0c;用户体验不佳&#xff0c;不能满足用户…

迷你Ceph集群搭建(超低配设备)

我的博客原文链接&#xff1a;https://blog.gcc.ac.cn/post/2023/%E8%BF%B7%E4%BD%A0ceph%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA/ 环境 机器列表&#xff1a; IP角色说明10.0.0.15osdARMv7&#xff0c;512M内存&#xff0c;32G存储&#xff0c;百兆网口10.0.0.16clientARM64…

【C语言】atoi函数的模拟

atoi对于初学者来说大概率是一个陌生的函数 但不要害怕&#xff0c;我们可以通过各种网站去查询 例如&#xff1a; cplusplus就是一个很好的查询网站 目录 函数介绍模拟实现需要注意的点 函数介绍 我们发现这是一个将字符串转换为整形数字的函数 例如&#xff1a; int main()…

C++类和对象(下)

目录 一、初始化列表 二、单参构造参数和explicit关键字 三、匿名对象 四、static成员 五、友元 六、内部类 一、初始化列表 之前我们在构造函数中写得还不错&#xff0c;也没发现什么问题&#xff0c;为什么C还有搞一个初始化列表呢&#xff1f; 如下这段代码&#x…

FPGA project : sobel

实验目标&#xff1a; sobel算法&#xff0c;处理100X100灰度图像&#xff1a;野火logo 边缘检测&#xff1a; 边缘检测&#xff0c;针对的是灰度图像&#xff0c;顾名思义&#xff0c;检测图像的边缘&#xff0c;是针对图像像素点的一种计算&#xff0c;目的是标识数字图像…

vue3学习(二)--- ref和reactive

文章目录 ref1.1 ref将基础类型和对象类型数据转为响应式1.2 ref()获取id元素1.3 isRef reactive1.1 reactive()将引用类型数据转为响应式数据&#xff0c;基本类型无效1.2 ref和reactive的联系 toRef 和 toRefs1.1 如果原始对象是非响应式的就不会更新视图 数据是会变的 ref …

点燃市场热情,让产品风靡全球——实用推广策略大揭秘!

文章目录 一、实用推广策略的重要性1. 提高产品知名度和认可度2. 拓展产品市场和用户群体3. 增强企业品牌形象和市场竞争力 二、实用推广策略的种类1. 社交媒体推广2. 定向推广3. 口碑营销4. 内容推广 三、实用推广策略的实施步骤1. 研究目标用户和市场需求&#xff0c;明确产品…

大数据NoSQL数据库HBase集群部署

目录 1. 简介 2. 安装 1. HBase依赖Zookeeper、JDK、Hadoop&#xff08;HDFS&#xff09;&#xff0c;请确保已经完成前面 2. 【node1执行】下载HBase安装包 3. 【node1执行】&#xff0c;修改配置文件&#xff0c;修改conf/hbase-env.sh文件 4. 【node1执行】&#xf…

第14章总结:lambda表达式与处理

14.1&#xff1a; lambada表达式 14.1.1&#xff1a;lambada表达式简介 无参数 package fourteen; interface SayhHi{ String say();//抽象方法接口 } public class NoParamDemo { public static void main(String[] args) { //无参数 …

【AI视野·今日Robot 机器人论文速览 第五十二期】Wed, 11 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Wed, 11 Oct 2023 Totally 31 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers RoboHive: A Unified Framework for Robot Learning Authors Vikash Kumar, Rutav Shah, Gaoyue Zhou, Vincent Moens, Vittor…

SAP MM学习笔记35 - 请求书照合中的差额处理(发票扣减,受入)

SAP中&#xff0c;请求书照合之后&#xff0c;发现不一致&#xff0c;就会支付保留。 支付保留&#xff0c;可以参考如下文章。 SAP MM学习笔记34 - 请求书照合中的支付保留&#xff08;发票冻结&#xff09;_东京老树根的博客-CSDN博客 即使支付保留之后暂时不付钱&#xff…

外卖跑腿系统开发的最佳实践和成功案例

外卖跑腿系统的开发既涉及技术实现&#xff0c;也需要考虑用户体验、运营策略和合规性。以下是一些最佳实践和一些成功的案例&#xff0c;以帮助您更好地理解这个领域的要点。 1. 技术框架的选择 选择适合的技术框架是外卖跑腿系统成功的关键。您可以考虑使用以下技术&#…

mysql面试题45:读写分离常见方案、哪些中间件可以实现读写分离

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说你知道的读写分离常见方案 读写分离是一种常见的数据库架构方案,旨在分担数据库的读写压力,提高系统的性能和可扩展性。以下是两种常见的…