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.rhkb.cn/news/157305.html

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

相关文章

java中的泛型

一 为啥要有泛型 泛型这个概念是在Java1.5提出来的&#xff0c;之前是没有的&#xff0c;那为什么之前没有&#xff0c;现在要提出来这个概念呢&#xff1f;那你就得想啊&#xff1a; 1、要么是之前的技术太垃圾了&#xff0c;得升级换代下 2、要么是技术发展&#xff0c;搞出…

【数据结构】算法的空间复杂度

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 算法空间复杂度的定义 算法的时间复杂度和空间复杂度是度量算法好坏的两个重要量度,在实际写代码的过程中,我们完全可以用空间来换时间,比如说,我们要判断某某年是不是闰年,大…

github创建个人网页登录后404无法显示的问题

1.首先必须要有内容&#xff0c;默认是会找index.html文件&#xff0c;找不到该文件会找readme.md文件&#xff0c;也就是说最简单的方法是&#xff0c;创建了与用户名同名的repository后username.github.io后&#xff0c;添加一个readme.md文件&#xff0c;得在readme里打点字…

【算法-动态规划】最长上升子序列-力扣 300

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

设计模式-相关内容

文章目录 一、设计模式概述二、UML图1.类的表示方法2.类与类之间关系的表示方法(1)关联关系(2)聚合关系(3)组合关系(4)依赖关系(5)继承关系(6)实现关系 三、软件设计原则1.开闭原则2.里氏代换原则3.依赖倒转原则4.接口隔离原则5.合成复用原则6.迪米特法则 一、设计模式概述 创…

驾驶数字未来:汽车业界数字孪生技术的崭新前景

随着数字化时代的到来&#xff0c;汽车行业正经历着前所未有的变革。数字孪生技术&#xff0c;作为一种前沿的数字化工具&#xff0c;正在为汽车行业带来革命性的影响&#xff0c;不仅改变着汽车制造和维护的方式&#xff0c;也为消费者带来了前所未有的体验。让我们一起探讨&a…

[sqoop]hive导入mysql,其中mysql的列存在默认值列

一、思路 直接在hive表中去掉有默认值的了列&#xff0c;在sqoop导入时,指定非默认值列即可&#xff0c; 二、具体 mysql的表 hive的表 create table dwd.dwd_hk_rcp_literature(id string,literature_no string,authors string,article_title string,source_title string…

OLED透明拼接屏:福州鼓山风景区:徜徉于城市壮丽之

福州是中国福建省的省会城市&#xff0c;历史悠久&#xff0c;文化底蕴深厚。 该城市曾是唐宋时期的重要港口城市&#xff0c;也是丝绸之路海上丝绸之路的起点之一。 福州以其独特的地理位置和丰富的历史遗产而闻名于世。 福州拥有众多的历史景点&#xff0c;其中最著名的是…

MySQL为什么用b+树

索引是一种数据结构&#xff0c;用于帮助我们在大量数据中快速定位到我们想要查找的数据。 索引最形象的比喻就是图书的目录了。注意这里的大量&#xff0c;数据量大了索引才显得有意义&#xff0c;如果我想要在[1,2,3,4]中找到4这个数据&#xff0c;直接对全数据检索也很快&am…

动态盘转换为基本盘

问题描述 不小心将磁盘0&#xff08;C和D是基本盘&#xff0c;蓝颜色&#xff09;改成了动态盘&#xff08;C和D是动态盘&#xff0c;橘黄色&#xff09;&#xff1f;如何修改回来呢&#xff1f; 解决方案&#xff1a; 使用DiskGenius将动态磁盘转换为基本磁盘 操作之前一定…

风电光伏混合储能功率小波包分解、平抑前后波动性分析、容量配置、频谱分析、并网功率波动分析(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【java|golang】多字段排序以及排序规则

奖励最顶尖的 K 名学生 给你两个字符串数组 positive_feedback 和 negative_feedback &#xff0c;分别包含表示正面的和负面的词汇。不会 有单词同时是正面的和负面的。 一开始&#xff0c;每位学生分数为 0 。每个正面的单词会给学生的分数 加 3 分&#xff0c;每个负面的词…

智慧空开让用电更安全、管理更智能——电脑APP远程控制开合闸

安科瑞 崔丽洁 01 什么是低压断路器&#xff1f;低压断路器的定义是&#xff1a;能够接通、承载及分断正常电路条件下的电流&#xff0c;也能在规定的非正常电路条件&#xff08;过载、短路、特别是短路&#xff09;下接通、承载一定时间和分断电流的开关电器。 断路器的分类&…

拼多多商品品牌数据接口,拼多多商品详情数据接口,拼多多优惠券数据接口,拼多多API接口

拼多多商品品牌数据采集方法如下&#xff1a; 手动数据采集。直接在拼多多平台上搜索并手动复制商品数据&#xff0c;适合采集小批量的商品数据&#xff0c;但不适用于大规模数据采集。手动采集需要手动输入搜索词&#xff0c;进行筛选后再复制所需的数据&#xff0c;该方法需…

白银做期货还是做现货?

现货白银和期货白银都是保证金交易品种&#xff0c;都具有一定的资金杠杆&#xff0c;新手投资者在它们之间进行选择的时候&#xff0c;可能会遇到一定的困难&#xff0c;但是只要投资者真正了解过它们的区别&#xff0c;在选择时思路就会更加清晰&#xff0c;能够根据自己的实…

Stable Diffusion XL搭建

本文参考&#xff1a;Stable Diffusion XL1.0正式发布了&#xff0c;赶紧来尝鲜吧-云海天教程 Stable Diffision最新模型SDXL 1.0使用全教程 - 知乎 1、SDXL与SD的区别 &#xff08;1&#xff09;分辨率得到了提升 原先使用SD生成图片&#xff0c;一般都是生成512*512&…

VMWare配置桥接

一、设置网络模式 二、编辑网卡配置 ip配置的子网掩码和默认网关保持和宿主机一致&#xff0c;ip局域网内不冲突。 # cd /etc/sysconfig/network-scriptslsvim ifcfg-ens160 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOnone DEFROUTEyes IPV4_FAILURE_FATALno IP…

hive 之select 中文乱码

此处的中文乱码和mysql的库表 编码 latin utf 无关。 直接上案例。 有时候我们需要自定义一列&#xff0c;有时是汉字有时是字母&#xff0c;结果遇到这种情况了。 说实话看到这真是糟心。这谁受得了。 单独select 没有任何问题。 这是怎么回事呢&#xff1f; 经过一番检查&…

C++11新特性(lambda,可变参数模板,包装器,bind)

lambda表达式是什么&#xff1f;包装器又是什么&#xff1f;有什么作用&#xff1f;莫急&#xff0c;此篇文章将详细带你探讨它们的作用。很多同学在学习时害怕这些东西&#xff0c;其实都是方便使用的工具&#xff0c;很多情况下我们学这些新的东西觉得麻烦&#xff0c;累赘&a…

服务器数据恢复-DS5300存储raid5硬盘出现坏道离线的数据恢复案例

服务器数据恢复环境&#xff1a; 某单位一台DS5300存储&#xff0c;1个主机4个扩展柜&#xff0c;组建了2组RAID5&#xff08;一组27块硬盘&#xff0c;一组23块盘&#xff09;。27块盘的那组RAID5阵列存放Oracle数据库文件&#xff0c;存储系统一共分了11个卷。 服务器故障&a…