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

java redis锁_Java中Redis锁的实现[通俗易懂]由于具体业务场景的需求,需要保证数据在分布式环境下的正确更新,所以研究了一下Java中分布式锁的实现。Java分布式锁的实现方式主要有以下三种:数据库实现的乐观锁Redis实现的分布式锁Zookeeper实现的分布式锁其中,较常用的是前两种方式,但是数据库实现方式需要较多的数据库操作,所以最终选择的是用Redis实现分布式锁。最初考虑分布式锁的数据安全性的时候,只考虑到两点。第一,Redis锁需要…

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

d6f4303c369f

由于具体业务场景的需求,需要保证数据在分布式环境下的正确更新,所以研究了一下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锁在更复杂的场景中所需要考虑的。留待以后继续研究。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • IOS 微信获取accesstoken 40029

    IOS 微信获取accesstoken 40029微信授权根据code获取accesstoken时报40029不合法的code在浏览器中是可以获取到token的 因为在客户端@”https://api.weixin.qq.com/sns/oauth2/access_token?appid=%@&secret=%@&code=%@&grant_type=authorization_code”请求时重定向了,第二次的时候code参数差了导致

  • SuperMap 最佳路径分析流程

    SuperMap 最佳路径分析流程学SuperMap也有一段时间了,总结一下软件下载:请到超图技术资源中心:http://support.supermap.com.cn第一步:导入数据第二步:选择数据选择线的时候多选一点线,路径分析最重要的就是路第三步:构建二维网格设置二维网格第四步:测试最佳路径第五步:发布下载:supermap-iserver下载请到超图技术资源中心:http://support.sup…

  • python json转对象

    python json转对象json转对象是python比较常见的一个应用场景,如何来实现呢?这里会涉及json模块中的loads方法,具体操作如下:导入json包importjson自定义classclassResult:def__init__(self,token,userName,empCode):self.token=tokenself.userName=userNameself.empCode=empCode添加需转换j

    2022年10月26日
  • goland 2021.5 永久激活码【在线注册码/序列号/破解码】

    goland 2021.5 永久激活码【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • pycharm的激活码_通用破解码

    pycharm的激活码_通用破解码,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • 什么是UE4_unity3d和ue4区别

    什么是UE4_unity3d和ue4区别对于游戏的来说,最能够影响它的性能的,便是游戏程序的开发、维护工作了。游戏开发、维护工作都是通过游戏开发引擎来完成的,而U3D和UE4正是如今行业主流的两款游戏开发引擎。不同的游戏开发引擎在不同的游戏中使用,那么具体的u3d和ue4的区别是什么?小编常被想要进入游戏行业的同学问到,学U3D和UE4哪个更好?其实这主要是看你自己的就业倾向。下面小编就从两款游戏开发引擎的具体区别来讲讲我们该如何选择。…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号