Redis下分布式锁的实现
来源:互联网 发布:华住集团 知乎 编辑:程序博客网 时间:2024/06/12 01:22
本文可作为redis in action第六章的学习笔记
其实,对redis而言,锁和事务与watch等等是分不开的。
我们先来聊聊事务和watch
事务,在关系型事务上的意思就是:一个事务内的sql,要不全部都执行成功,要么全部都不执行。
不过redis的事务只能部分满足"一荣俱荣,一损俱损"的特性。
怎么说?
在关系型数据库中,一个事务内部如果发生了错误,所有sql就都回滚到初始状态
在redis的事务中,所谓的错误,至少分两种:
1 语法错误
例如 我把set dlf abc 写成了sett dlf abc
2 运行错误
dlf这是个String类型的key,sadd这是对set类型数据做操作的
命令:sadd dlf kkk也就会出错,这就是运行错误
如果在一个事务中,一共三个命令,第二个命令有语法错误,那么三条命令就等于都没有执行。
如果在一个事务中,一共三个命令,第二个命令有运行错误,那么第一三条命令还是执行了的。
所以大家得尽力解决运行错误,你得记得每一个键都是什么类型的。
我记不住呀!
你说你记不住?那你还写什么代码?不会回家看孩子去。
watch命令
watch一般也是跟实物连用的。
线程A,watch某个数据后,如果在线程A执行exec之前,线程B修改了那个数据,那么线程A的事务就会失败。
与此同时,线程B修改的那个数据也已经进入redis了。
所以一般情况下,watch都会包含在一个while循环中。
如果负载很大,事务就会不断的重试!
数据库的那种锁叫悲观锁,我用的时候你不能用。
redis的这个watch机制,叫做乐观锁,就是假定不会有人来打扰我,如果有人打扰(修改了我要操作的数据)我了,那就以别人的数据为准,我再做一遍。
那有什么办法呢?
锁!!
另一方面
我们抛开redis暂且不谈,在单机情况下,java的synchronized关键词能保证同步性。那么多机下呢?
就得用分布式锁了。
也就说,不管是从redis的watch的多次重试上来说,还是从多机互斥上来说,我们都得有一个能支持分布式的不会重试的锁!
OK,我们先说一个命令
SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能:
当且仅当 key 不存在,将key的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
我们设计的redis锁,准确的说保存的就是一个字符串
key是锁的名字,value是一个uuid
OK,开始上代码
下面就是释放锁了:
2:16的时候,线程b想要获得锁,那肯定是获得不了的,线程b阻塞到那了。
到2:18的时候,线程A还没有主动释放锁,但是锁的过期时间已经到了,redis已经删除了那个锁
到2:19的时候线程b就已经获得锁了,返回的uuid假如是edf,并且过期时间也是3分钟,在2:22之前,理论上,只有线程b持有锁。
然后到2:20的时候,线程A来释放锁。如果不检查两个identifier是否相等,线程a就把线程b的锁给删除了
然后2:21线程c就获得了自己本不应该获得的锁(此时线程锁还应该在线程b手上)
亲爱的朋友们,你们明白了么。
那具体怎么使用这个锁呢?
参考资料
http://qifuguang.me/2015/09/30/Redis%E4%BA%8B%E5%8A%A1%E4%BB%8B%E7%BB%8D/
https://my.oschina.net/OutOfMemory/blog/300173
http://blog.csdn.net/ugg/article/details/41894947
其实,对redis而言,锁和事务与watch等等是分不开的。
我们先来聊聊事务和watch
事务,在关系型事务上的意思就是:一个事务内的sql,要不全部都执行成功,要么全部都不执行。
不过redis的事务只能部分满足"一荣俱荣,一损俱损"的特性。
怎么说?
在关系型数据库中,一个事务内部如果发生了错误,所有sql就都回滚到初始状态
在redis的事务中,所谓的错误,至少分两种:
1 语法错误
例如 我把set dlf abc 写成了sett dlf abc
2 运行错误
dlf这是个String类型的key,sadd这是对set类型数据做操作的
命令:sadd dlf kkk也就会出错,这就是运行错误
如果在一个事务中,一共三个命令,第二个命令有语法错误,那么三条命令就等于都没有执行。
如果在一个事务中,一共三个命令,第二个命令有运行错误,那么第一三条命令还是执行了的。
所以大家得尽力解决运行错误,你得记得每一个键都是什么类型的。
我记不住呀!
你说你记不住?那你还写什么代码?不会回家看孩子去。
watch命令
watch一般也是跟实物连用的。
线程A,watch某个数据后,如果在线程A执行exec之前,线程B修改了那个数据,那么线程A的事务就会失败。
与此同时,线程B修改的那个数据也已经进入redis了。
所以一般情况下,watch都会包含在一个while循环中。
while (System.currentTimeMillis() < end) { conn.watch(inventory); Transaction trans = conn.multi(); //...进行事务操作 List<Object> results = trans.exec(); // 如果返回的是null 就说明因为watch的域被改变了 // 事务也就被打断了 if (results == null){ continue; } return true; }那while有什么问题呢?
如果负载很大,事务就会不断的重试!
数据库的那种锁叫悲观锁,我用的时候你不能用。
redis的这个watch机制,叫做乐观锁,就是假定不会有人来打扰我,如果有人打扰(修改了我要操作的数据)我了,那就以别人的数据为准,我再做一遍。
那有什么办法呢?
锁!!
另一方面
我们抛开redis暂且不谈,在单机情况下,java的synchronized关键词能保证同步性。那么多机下呢?
就得用分布式锁了。
也就说,不管是从redis的watch的多次重试上来说,还是从多机互斥上来说,我们都得有一个能支持分布式的不会重试的锁!
OK,我们先说一个命令
SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能:
当且仅当 key 不存在,将key的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
我们设计的redis锁,准确的说保存的就是一个字符串
key是锁的名字,value是一个uuid
OK,开始上代码
/** * 如果过了acquireTimeout时间后,我还没有获得锁,我就放弃了 * 同时直接返回null * 用户获得锁以后,最多使用lockTimeout长时间 过了之后 * 别的客户端 也就能取到锁了 */ public String acquireLockWithTimeout( Jedis conn, String lockName, long acquireTimeout, long lockTimeout) {// String identifier = UUID.randomUUID().toString(); String lockKey = "lock:" + lockName; int lockExpire = (int)(lockTimeout / 1000); long end = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < end) { //等于1 就说明之前并没有这个key if (conn.setnx(lockKey, identifier) == 1){ conn.expire(lockKey, lockExpire); return identifier; } //代码运行到这里 说明锁已经被别人拿走了 //等于-1 表示之前也没有设置超时时间 if (conn.ttl(lockKey) == -1) { conn.expire(lockKey, lockExpire); } try {//等1毫秒再试 Thread.sleep(1); }catch(InterruptedException ie){ Thread.currentThread().interrupt(); } } //别人拿到锁了 我等的时间太久了 老子不等了 return null; }如果返回的值不为null,那么就说明获得锁了,而且获得的那个identifier,在释放锁的时候也有用。
下面就是释放锁了:
/** * 释放成功 返回true 反之返回false * @param conn * @param lockName * @param identifier * @return */ public boolean releaseLock(Jedis conn, String lockName, String identifier) { String lockKey = "lock:" + lockName; while (true){ conn.watch(lockKey); //如果不相等 会怎么样 为什么有这一步的检查 if (identifier.equals(conn.get(lockKey))){ Transaction trans = conn.multi(); trans.del(lockKey); List<Object> results = trans.exec(); if (results == null){ continue; } return true; } //unwatch 没有进入事务 就得手动unwatch conn.unwatch(); break; } return false; }我自己再看到这个代码的时候,很疑惑identifier.equals(conn.get(lockKey)这个是干什么?
就算别人把我的锁的identifier从2dsafd改成了2fsae,能咋么?我直接删除了就是了么。反正只要我不还锁,别人都无法获得锁,能有啥问题么。
2:16的时候,线程b想要获得锁,那肯定是获得不了的,线程b阻塞到那了。
到2:18的时候,线程A还没有主动释放锁,但是锁的过期时间已经到了,redis已经删除了那个锁
到2:19的时候线程b就已经获得锁了,返回的uuid假如是edf,并且过期时间也是3分钟,在2:22之前,理论上,只有线程b持有锁。
然后到2:20的时候,线程A来释放锁。如果不检查两个identifier是否相等,线程a就把线程b的锁给删除了
然后2:21线程c就获得了自己本不应该获得的锁(此时线程锁还应该在线程b手上)
亲爱的朋友们,你们明白了么。
那具体怎么使用这个锁呢?
String locked = acquireLockWithTimeout(conn, lockName,1000);//你的代码 releaseLock(conn, identifier, locked);
参考资料
http://qifuguang.me/2015/09/30/Redis%E4%BA%8B%E5%8A%A1%E4%BB%8B%E7%BB%8D/
https://my.oschina.net/OutOfMemory/blog/300173
http://blog.csdn.net/ugg/article/details/41894947
0 0
- Redis下分布式锁的实现
- Redis实现分布式环境下的分布式锁机制
- Redis实现分布式环境下的分布式锁机制
- Redis实现分布式环境下的分布式锁机制
- redis分布式锁的实现
- redis实现的分布式锁
- redis分布式锁的实现
- 分布式锁的redis实现
- 【Redis深入】Redis分布式锁的实现
- 基于Redis实现分布式锁(下)
- Redis实现分布式锁
- Redis 分布式锁实现
- Redis 分布式锁实现
- Redis实现分布式锁
- redis实现分布式锁
- Redis实现分布式锁
- 分布式锁redis实现
- Redis 分布式锁实现
- JAVAjdk目录结构简单介绍
- 算法导论第十五章15-2最长回文子序列
- 周易六十四卦——泽天夬卦
- WPF教程(二十五)WrapPanel
- xml
- Redis下分布式锁的实现
- 业务
- 安装Ubuntu Kylin 16.04 UKUI预览版后要做的事
- Codeforce 598E(递推)
- 正则表达式
- 语言模型-SRILM(1) 安装
- 指针
- Struts2知识点
- CodeForces 731B Coupons and Discounts