redis分布式锁-SETNX实现

转自:https://my.oschina.net/u/1995545/blog/366381

Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。这系列的命令非常有用,这里讲使用SETNX来实现分布式锁。 

用SETNX实现分布式锁 
利用SETNX非常简单地实现分布式锁。例如:某客户端要获得一个名字foo的锁,客户端使用下面的命令进行获取: 
SETNX lock.foo <current Unix time + lock timeout + 1> 

  • 如返回1,则该客户端获得锁,把lock.foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁。

  • 如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。



解决死锁 
上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于lock.foo的值,说明该锁已失效,可以被重新使用。 

发生这种情况时,可不能简单的通过DEL来删除锁,然后再SETNX一次,当多个客户端检测到锁超时后都会尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景: 

C0操作超时了,但它还持有着锁,C1和C2读取lock.foo检查时间戳,先后发现超时了。 
C1 发送DEL lock.foo 
C1 发送SETNX lock.foo 并且成功了。 
C2 发送DEL lock.foo 
C2 发送SETNX lock.foo 并且成功了。 
这样一来,C1,C2都拿到了锁!问题大了! 

幸好这种问题是可以避免的,让我们来看看C3这个客户端是怎样做的: 

C3发送SETNX lock.foo 想要获得锁,由于C0还持有锁,所以Redis返回给C3一个0 
C3发送GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试。 
反之,如果已超时,C3通过下面的操作来尝试获得锁: 
GETSET lock.foo <current Unix time + lock timeout + 1> 
通过GETSET,C3拿到的时间戳如果仍然是超时的,那就说明,C3如愿以偿拿到锁了。 
如果在C3之前,有个叫C4的客户端比C3快一步执行了上面的操作,那么C3拿到的时间戳是个未超时的值,这时,C3没有如期获得锁,需要再次等待或重试。留意一下,尽管C3没拿到锁,但它改写了C4设置的锁的超时值,不过这一点非常微小的误差带来的影响可以忽略不计。 

注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。 

示例伪代码 
根据上面的代码,我写了一小段Fake代码来描述使用分布式锁的全过程: 

# get lock 
lock = 0 
while lock != 1: timestamp = current Unix time + lock timeout + 1 lock = SETNX lock.foo timestamp if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)): break; else: sleep(10ms) # do your job 
do_job() # release 
if now() < GET lock.foo: DEL lock.foo 


是的,要想这段逻辑可以重用,使用python的你马上就想到了Decorator,而用Java的你是不是也想到了那谁?AOP + annotation?行,怎样舒服怎样用吧,别重复代码就行。 

java之jedis实现 
expireMsecs 锁持有超时,防止线程在入锁以后,无限的执行下去,让锁无法释放 
timeoutMsecs 锁等待超时,防止线程饥饿,永远没有入锁执行代码的机会    

/*** Acquire lock.* * @param jedis* @return true if lock is acquired, false acquire timeouted* @throws InterruptedException*             in case of thread interruption*/public synchronized boolean acquire(Jedis jedis) throws InterruptedException {int timeout = timeoutMsecs;while (timeout >= 0) {long expires = System.currentTimeMillis() + expireMsecs + 1;String expiresStr = String.valueOf(expires); //锁到期时间if (jedis.setnx(lockKey, expiresStr) == 1) {// lock acquiredlocked = true;return true;}String currentValueStr = jedis.get(lockKey); //redis里的时间if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {//判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的// lock is expired
String oldValueStr = jedis.getSet(lockKey, expiresStr);//获取上一个锁到期时间,并设置现在的锁到期时间,//只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {//如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁// lock acquiredlocked = true;return true;}}timeout -= 100;Thread.sleep(100);}return false;}

 

一般用法 
其中很多繁琐的边缘代码 
包括:异常处理,释放资源等等        

JedisPool pool;JedisLock jedisLock = new JedisLock(pool.getResource(), lockKey, timeoutMsecs, expireMsecs);try {if (jedisLock.acquire()) { // 启用锁//执行业务逻辑} else {logger.info("The time wait for lock more than [{}] ms ", timeoutMsecs);}} catch (Throwable t) {// 分布式锁异常
            logger.warn(t.getMessage(), t);} finally {if (jedisLock != null) {try {jedisLock.release();// 则解锁} catch (Exception e) {}}if (jedis != null) {try {pool.returnResource(jedis);// 还到连接池里} catch (Exception e) {}}}

 

 

犀利用法 
用匿名类来实现,代码非常简洁 
至于SimpleLock的实现

       
 SimpleLock lock = new SimpleLock(key);lock.wrap(new Runnable() {@Overridepublic void run() {//此处代码是锁上的
            }});

 

转载于:https://www.cnblogs.com/SimplifyIT/p/6691584.html

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

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

相关文章

sql java驱动程序_Microsoft SQL Server JDBC 驱动程序支持矩阵

本页包含 Microsoft SQL Server JDBC 驱动程序的支持矩阵和支持生命周期策略。Microsoft JDBC 驱动程序支持生命周期矩阵和策略Microsoft 支持生命周期 (MSL) 策略提供了与 Microsoft 产品的支持生命周期有关的可预测透明信息。 自驱动程序发布之日起&#xff0c;JDBC 驱动程序…

使用直接内存时可以更快

总览 使用直接内存不能保证提高性能。 考虑到它增加了复杂性&#xff0c;除非有充分的理由使用它&#xff0c;否则应避免使用它。 塞尔吉奥奥利维拉&#xff08;Sergio Oliveira Jr&#xff09;的这篇出色文章表明&#xff0c;这不仅仅是使用直接内存来提高性能的问题&#x…

POJ 3977 折半枚举

链接&#xff1a; http://poj.org/problem?id3977 题意&#xff1a; 给你n个数&#xff0c;n最大35&#xff0c;让你从中选几个数&#xff0c;不能选0个&#xff0c;使它们和的绝对值最小 如果有一样的&#xff0c;取个数最小的 题解&#xff1a; np难题&#xff0c;但是因为…

java踩坑记

1.String 相等 稍微有点经验的程序员都会用equals比较而不是用 &#xff0c;但用equals就真的安全了吗&#xff0c;看下面的代码 user.getName().equals("xiaoming"); 有经验的老司机很快就能看到问题&#xff0c;如果user.getName()为null,就会抛出空指针异常&#…

java taken_java-是否有正确的方法在slf4j中传递参数?

第三变种是最好的。实际上&#xff0c;第一种情况是通过StringBuilder进行的字符串连接。第二和第三种情况相同。他们需要将整数值装箱到Integer(或其他Object)&#xff0c;然后创建一个数组来打包它们。在我的机器上进行的简单测试表明&#xff0c;如果不执行日志记录&#xf…

html常用小知识

请求重定向&#xff1a;加载页面之后&#xff0c;除了用js做重定向之外&#xff0c;我们还可以直接用<meta>标签做重定向。 1 <meta http-equiv"refresh" content"5;urlhttp://www.baidu.com" /> 5秒后跳转 超链接&#xff1a;在当前的iframe…

MyEclipse快捷键大全【转】

-------------------------------------MyEclipse 快捷键1(CTRL)-------------------------------------Ctrl1 快速修复CtrlD: 删除当前行 CtrlQ 定位到最后编辑的地方 CtrlL 定位在某行 CtrlO 快速显示 OutLine CtrlT 快速显示当前类的继承结构 CtrlW 关闭当前Editer Ct…

根据您的命令-命令设计模式

命令设计模式是一种广为人知的设计模式&#xff0c;它属于行为设计模式&#xff08;“四人帮”的一部分&#xff09;。 顾名思义&#xff0c;它与应用程序中的动作和事件有关。 问题陈述&#xff1a; 假设有一个网页将在其中包含多个菜单的情况。 编写此代码的一种方法是使条件…

用js和jQuery做轮播图

Javascript或jQuery做轮播图 css样式 <style> a{ text-decoration:none; } .naver{ width: 100%; position:relative; }.images{position:relative;width: 100%;height: 400px; } .images img{position:absolute;left: 0;top: 0;width: 100%;height: 400px;opacity: 0;fi…

w3school前端教程合集

有关前端开发的w3c教程合集。 http://caibaojian.com/w3school/ 地图ajax教程Canvas教程CSS教程CSS3教程CSS3选择器CSS参考手册DHTML教程HTML教程HTML5教程HTML5音频教程HTML DOM教程JavaScript教程jQuery教程jQuery Ajax教程jQuery事件jQuery操作jQuery选择器jQuery遍历json教…

【开发调试】谷歌浏览器中调试移动网页和测试网速下页面效果

、 今天有幸给大家分享一下谷歌浏览器针对移动网页测试的技巧&#xff0c;主要是最近做个微信公共号网站。所以就要对页面测试拉。移动网页我们最长测得就是各种手机大小的页面效果和出现网络问题的效果展示。 今天就简单分享下在谷歌浏览器测试页面的适配和网速限制展示。…

拼多多分享好友砍价Java实现_拼多多砍价怎么分享到朋友圈 砍价发到微信朋友圈方法...

拼多多是一款电商社交的共享式购物平台&#xff0c;现在还推出了砍价的活动&#xff0c;只要邀请好友砍价&#xff0c;你就以最低的价格购买商品&#xff0c;还可以可能是免费拿到&#xff0c;那么今天小编就给大家讲讲如何将自己的砍价信息分享到微信朋友圈。首先下载手机拼多…

通过6个简单的步骤在Windows上运行Apache Hive

注意 &#xff1a;您需要安装cygwin才能运行本教程&#xff0c;因为Hadoop&#xff08;Hive需要&#xff09;需要cygwin才能在Windows上运行。 至少&#xff0c;系统中必须存在Basic&#xff0c;Net&#xff08;OpenSSH&#xff0c;tcp_wrapper软件包&#xff09;和与安全相关的…

vim编辑器初级(八)

:abbreviate  后面接一个缩写&#xff0c;再接这个缩写的全写&#xff0c;这样在输入这个缩写后&#xff0c;vim会自动将其展开为它的全写 :abbreviate  列出目前你所设置的所有缩写 :map  后面接一个字符串&#xff0c;再接这个字符串所映射的一串命令&#xff0c;这样在…

java多文件post请求_如何使用Java发出多部分/表单数据POST请求?

我们使用HttpClient 4.x创建多部分文件post。更新&#xff1a;截至HttpClient 4.3&#xff0c;一些类已被弃用。下面是新API的代码&#xff1a;CloseableHttpClient httpClient HttpClients.createDefault();HttpPost uploadFile new HttpPost("...");MultipartEnt…

vue 环境的搭建及初始化项目

其实超级简单&#xff0c;虽然网上很多&#xff0c;但是我顺便记录下相当于做笔记吧 1nodejs 的安装&#xff0c; 在node官网下载&#xff0c;点击安装&#xff0c;安装的时候最好选择路径在d盘 2设置环境变量 我的电脑-->属性-->系统环境变量- 系统变量新增一个NODE…

Java堆转储:您可以完成任务吗?

如果您像我一样对Java性能充满热情&#xff0c;那么堆转储分析对您来说应该不是一个谜。 如果是这样&#xff0c;那么好消息是您将有机会提高您的Java故障诊断技能和JVM知识。 JVM现已发展到今天&#xff0c;与旧的JDK 1.0 – JDK 1.4天相比&#xff0c;今天生成和分析JVM堆转…

MariaDB配置、集群

MariaDB在centos 7.3的安装&#xff0c;配置和集群搭配 阿里云最新选配系统中&#xff0c;只有centos7.3可选&#xff0c;因此&#xff0c;基于centos 7的MariaDB的安装&#xff0c;配置。。。 全部删除MySQL/MariaDB MySQL 已经不再包含在 CentOS 7 的源中&#xff0c;而改用了…

java 调用 ictclas50_1-Ictclas50分词系统ForJava

Ictclas50是一个分词库&#xff0c;我嘛主要用来做中文分词&#xff0c;其也能分出词性等东西。1.环境搭建进入到下载页面进行下载&#xff1a;如下图&#xff1a; 因为我的系统是64位的windows&#xff0c;所以选择了到数第三行进行下载。其JAVA版本是通过JNI去调用dll库&…

SpringMVC乱码或前台乱码解决办法

JSP页面乱码 <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> 以及 form表单提交方式为必须为post 修改web.xml&#xff0c;增加编码过滤器&#xff0c;如下&#xff08;注意&#xff0c;需要设置forceEncoding参数值…