Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >java redis锁_Java中Redis锁的实现[通俗易懂]

java redis锁_Java中Redis锁的实现[通俗易懂]

作者头像
全栈程序员站长
发布于 2022-09-10 09:55:19
发布于 2022-09-10 09:55:19
2.1K0
举报

大家好,又见面了,我是你们的朋友全栈君。

由于具体业务场景的需求,需要保证数据在分布式环境下的正确更新,所以研究了一下Java中分布式锁的实现。

Java分布式锁的实现方式主要有以下三种:

数据库实现的乐观锁

Redis实现的分布式锁

Zookeeper实现的分布式锁

其中,较常用的是前两种方式,但是数据库实现方式需要较多的数据库操作,所以最终选择的是用Redis实现分布式锁。

最初考虑分布式锁的数据安全性的时候,只考虑到两点。第一,Redis锁需要有一个超时时间,这样即便某个持有锁的节点挂了,也不到导致其他节点死锁,保证每个锁有一个UniqueId;第二,每个锁需要有一个UniqueId,确保当一个线程执行完一个任务去释放锁的时候释放的一定是自己的锁,否则可能存在一种场景,就是一个线程释放锁的时候,它的锁可能已经超时被释放了,而因为缺少一个UniqueId,它却释放了另一个线程的锁

基于以上两点的考虑,分别设计了获取锁和释放锁的api。

public interface DistributionLockService {

/**

* @param lockName the name of the lock

* @param uniqueCode uniqueCode for the lock

* @param expireTime expire time of lock(MILLISECONDS)

* @return the result of get lock

* */

boolean getLock(String lockName, String uniqueCode, int expireTime);

/**

* @param lockName the name of the lock

* @param uniqueCode uniqueCode for the lock

* */

void releaseLock(String lockName, String uniqueCode);

}

具体的实现代码如下:

/**

* @param lockName the name of the lock

* @param uniqueCode uniqueCode for the lock

* @param expireTime expire time of lock(MILLISECONDS)

* @return the result of get lock

* */

@Override

public boolean getLock(String lockName, String uniqueCode, int expireTime) {

boolean isLock = false;

try {

Long result = jedis.setnx(lockName, uniqueCode);

isLock = result == 1 ? true : false;

if (isLock) {

jedis.expire(lockName, expireTime);

}

} catch (Exception e){

logger.error(“DistributionLockService/getLock”, e);

}

return isLock;

}

/**

* @param lockName the name of the lock

* @param uniqueCode uniqueCode for the lock

* */

@Override

public void releaseLock(String lockName, String uniqueCode) {

try {

String tag = jedis.get(getKey(lockName));

if (tag != null && tag.equals(uniqueCode))

jedis.del(getKey(lockName));

} catch (Exception e) {

logger.error(“DistributionLockService/releaseLock”, e);

}

}

上述的代码用setnx+expire实现分布式锁。调用setnx,当传入的key未被占用时,就在redis中插入一条该key的记录,返回值为1,此时为其设置超时时间。而当这个key在redis中已有记录时,则不会重新插入记录,这样的话,便可以实现分布式锁的基本功能。且为其设置过期时间,并加入UniqueId的check,避免了上述提及的两个问题。

但是,上述代码仍然存在问题,就是忽略了操作的原子性。获取锁的时候,调sexnx方法与设置超时时间expire不是原子操作,如果在sexnx方法执行成功后,节点突然down掉,没有执行expire方法,而之后的释放锁操作也没有执行,那么这个节点便会长期持有锁,尽管这种可能性很小,但是依然存在死锁的风险。为了避免这种风险,修正代码如下:

private static final String SET_IF_NOT_EXIST = “NX”;

private static final String SET_WITH_EXPIRE_TIME = “PX”;

private static final String IS_LOCKED = “OK”;

/**

* @param lockName the name of the lock

* @param uniqueCode uniqueCode for the lock

* @param expireTime expire time of lock

* @return the result of get lock

* */

@Override

public boolean getLock(String lockName, String uniqueCode, int expireTime) {

boolean isLock = false;

try {

String result = jedis.set(lockName, uniqueCode, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

isLock = IS_LOCKED.equalsIgnoreCase(result) ? true : false;

} catch (Exception e){

logger.error(“DistributionLockService/getLock”, e);

}

return isLock;

}

Redis较高的版本中,有一个有五个参数的set方法,其中前两个参数就是key和value,最后一个参数是过期时间,中间两个参数表示setnx和setex,实际上就是一个可以设置过期时间的setnx方法。这个方法可以保证加锁和设置过期时间两者是作为一个请求传送到Redis服务器的,所以不会出现上述的死锁场景。

加锁的问题解决了,解锁的问题依然在。上述的解锁代码中,在解锁之前先验证了UniqueId,然后采用del方法来释放锁,但是由于get和del是两次请求,而不是一个原子操作,所以这之间仍存在并发的问题。若做check的时候,检查得到确实是这个锁的UniqueId,但是在执行del方法之前,这个锁已经超时,然后新的线程也已经获取到锁了,那么del删掉的锁,便不是自己的锁,而是下一个线程的锁。

Redis中没有直接的api处理这个问题。解决这个问题,需要使用lua脚本,来确保整个操作的原子性。代码如下:

/**

* @param lockName the name of the lock

* @param uniqueCode uniqueCode for the lock

* */

@Override

public void releaseLock(String lockName, String uniqueCode) {

try {

String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] ” +

“then return redis.call(‘del’, KEYS[1]) ” +

“else return 0 end”;

jedis.eval(script, Arrays.asList(lockName), Arrays.asList(uniqueCode));

} catch (Exception e) {

logger.error(“DistributionLockService/releaseLock”, e);

}

}

jedis的eval方法支持执行lua脚本方法,所以便可利用这个方法来实现释放锁的原子操作,具体逻辑和之前的代码其实是一致的,但是由于是原子操作,所以可以避免上文中存在的问题。

至此,简单Redis锁的实现便算是成功了。但是其中依然存在许多问题,如果Redis不是单机的,而是集群分布的,那么其中的数据同步该怎么做?在有些较看重数据的正确性的场景中,即使Redis锁超时,只要检测到机器仍在正常运行Redis锁就不应该被释放,而应该被续期,这些,都是redis锁在更复杂的场景中所需要考虑的。留待以后继续研究。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/149708.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何优雅的使用Redis实现分布式锁
我们在多线程开发过程中,肯定没避免不了使用锁,jdk中也提供了大量的锁功能,但是我们为什么还要手动开发一个分布式锁呢,原因在于我们在传统项目中使用的锁是在同一个进程中的,他们能够相互访问到彼此的资源信息,但是在分布式中,每个项目都是跑在不同的进程中的,他们无法共享资源信息,所以就需要一个能够在不同进程之间进行“通信”的第三方来实现这个功能,那么redis其实就具备这种功能的。
AI码师
2020/11/19
9260
如何优雅的使用Redis实现分布式锁
Redis 分布式锁(14)
在分布式系统中,有些业务场景会用到分布式锁,实现分布式锁的方式有很多,本篇主要讲根据Redis如何来实现。
兜兜毛毛
2021/05/18
5370
Redis 分布式锁(14)
分布式锁系列--03关于分布式锁的选型分析
本文分析,在分布式系统中,使用redis实现分布式锁,会遇到什么问题。关于分布式锁概念和redis分布式锁的具体实现,可参考前面的2篇文章。本文重点在于,对分布式锁技术选型的分析。
IT云清
2019/03/04
3970
redis分布式锁的实现(setNx命令和Lua脚本)
本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结
全栈程序员站长
2022/08/31
2.5K0
分布式锁系列--02Redis实现分布式锁
有一个redis服务实例,在分布式系统中,所有需要获取锁的客户端,都需要访问这个redis实例:
IT云清
2019/01/22
5920
Redis分布式锁的正确实现方式(Java版)
https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/
全栈程序员站长
2022/09/05
1.4K0
redis实现分布式锁的原理_Redis作为分布式锁原理
现在面试,一般都会聊聊分布式系统这块的东西。通常面试官都会从服务框架(Spring Cloud、Dubbo)聊起,一路聊到分布式事务、分布式锁、ZooKeeper等知识。
全栈程序员站长
2022/11/17
1.1K0
redis实现分布式锁的原理_Redis作为分布式锁原理
大厂-分布式专栏 23 分布式系统下分布式锁的实现
锁是开发过程中十分常见的工具,你一定不陌生,悲观锁,乐观锁,排它锁,公平锁,非公平锁等等,很多概念,如果你对java里的锁还不了解,可以参考这一篇:不可不说的Java“锁”事,这一篇写的很全面了,但是对于初学者,知道这些锁的概念,由于缺乏实际工作经验,可能并不了解锁的实际使用场景,Java中可以通过Volatile、Synchronized、ReentrantLock 三个关键字来实现线程的安全,这部分知识在第一轮基础面试里一定会问(要熟练掌握哦)。
botkenni
2022/08/25
4240
【110期】面试官:Redis分布式锁如何解决锁超时问题?
关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完的问题, 这样会导致: A线程超时时间设为10s(为了解决死锁问题), 但代码执行时间可能需要30s, 然后redis服务端10s后将锁删除, 此时, B线程恰好申请锁, redis服务端不存在该锁, 可以申请, 也执行了代码, 那么问题来了, A、B线程都同时获取到锁并执行业务逻辑, 这与分布式锁最基本的性质相违背: 在任意一个时刻, 只有一个客户端持有锁, 即独享。
良月柒
2021/01/07
9200
【110期】面试官:Redis分布式锁如何解决锁超时问题?
关于分布式锁的面试题都在这里了
最简单的理由就是作为一个社招程序员,面试的时候一定被面啦,你看怎么多公众号都翻来覆去的发分布式锁的主题,可见它很重要啦,在高考里这就是送分题,不要怪可惜的。
王炸
2020/04/26
3.2K0
剖析分布式锁
前不久,阿里大牛虾总再次抛出了分布式锁的讨论,对照之前项目中实现的redis分布式锁总结一下
码农戏码
2021/03/23
4110
Redis 分布式锁应用
Redis 最常使用的场景是作为缓存,缓存用户信息,会话信息,还有一些热点信息。
王小明_HIT
2019/08/12
9460
分布式锁实现大型连续剧之(一):Redis
单机环境下我们可以通过JAVA的Synchronized和Lock来实现进程内部的锁,但是随着分布式应用和集群环境的出现,系统资源的竞争从单进程多线程的竞争变成了多进程的竞争,这时候就需要分布式锁来保证。
java架构师
2018/09/26
1.2K0
谈谈几种分布式锁实现
在JVM中,可以使用同步锁或Lock锁,在多线程并发的情况下保证同一时间只有一个线程修改共享变量或执行代码块。然而,随着现代应用程序基本上都基于分布式集群来实现的趋势,传统Java锁在分布式环境中使用时就显得无能为力。此时,我们需要实现分布式锁来保证共享资源的原子性。分布式锁还可以用于避免不同节点执行重复的任务,例如在分布式集群中只需要保证一个服务节点发送短信,以避免多个节点重复发送短信给同一个用户,从而避免资源的浪费。
架构狂人
2023/08/16
2400
谈谈几种分布式锁实现
Redis(3)——分布式锁深入探究
如果 把一台服务器比作一个房子,那么 线程就好比里面的住户,当他们想要共同访问一个共享资源,例如厕所的时候,如果厕所门上没有锁...更甚者厕所没装门...这是会出原则性的问题的..
乔戈里
2020/03/13
5340
利用Redis实现防止接口重复提交功能
在划水摸鱼之际,突然听到有的用户反映增加了多条一样的数据,这用户立马就不干了,让我们要马上修复,不然就要投诉我们。
秃头哥编程
2022/04/27
1.3K0
框架篇:分布式锁
java有synchronize和Lock,mysql 修改类的sql也带有锁。锁定数据状态,让数据状态在并发场景,按我们预想逻辑进行状态转移,然而在分布式,集群的情况下,怎么去锁定数据状态呢
潜行前行
2021/06/25
6580
一个Redis分布式锁的实现引发的思考
再把视角移到 Redis 服务器来,就会发现 单点问题 的存在,此时分布式锁就无法使用了。
Java4ye
2024/06/17
1770
Redis分布式锁及其常见问题解决方案
在一个分布式系统中,当一个线程去读取数据并修改的时候,因为读取和更新保存不是一个原子操作,在并发时就很容易遇到并发问题,进而导致数据的不正确。这种场景很常见,比如电商秒杀活动,库存数量的更新就会遇到。如果是单机应用,直接使用本地锁就可以避免。如果是分布式应用,本地锁派不上用场,这时就需要引入分布式锁来解决。
栗筝i
2023/10/16
1.3K0
Redis分布式锁及其常见问题解决方案
开发实例:实现一个基于Redis的分布式锁
在分布式系统中,锁是非常常见的一种同步机制。当多个节点同时访问某个共享资源时,我们可以使用锁来保证数据的一致性和正确性。Redis是一个高性能的内存数据库,也为我们提供了很好的锁的解决方案。下面,我将为大家介绍如何使用Redis实现一种简单而高效的分布式锁。
用户1289394
2024/03/18
1600
开发实例:实现一个基于Redis的分布式锁
推荐阅读
相关推荐
如何优雅的使用Redis实现分布式锁
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档