`
m635674608
  • 浏览: 5041741 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

redis分布式锁-SETNX实现

 
阅读更多

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 锁等待超时,防止线程饥饿,永远没有入锁执行代码的机会

Java代码  收藏代码
  1. /** 
  2.  * Acquire lock. 
  3.  *  
  4.  * @param jedis 
  5.  * @return true if lock is acquired, false acquire timeouted 
  6.  * @throws InterruptedException 
  7.  *             in case of thread interruption 
  8.  */  
  9. public synchronized boolean acquire(Jedis jedis) throws InterruptedException {  
  10.     int timeout = timeoutMsecs;  
  11.     while (timeout >= 0) {  
  12.         long expires = System.currentTimeMillis() + expireMsecs + 1;  
  13.         String expiresStr = String.valueOf(expires); //锁到期时间  
  14.   
  15.         if (jedis.setnx(lockKey, expiresStr) == 1) {  
  16.             // lock acquired  
  17.             locked = true;  
  18.             return true;  
  19.         }  
  20.   
  21.         String currentValueStr = jedis.get(lockKey); //redis里的时间  
  22.         if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {  
  23.             //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的  
  24.             // lock is expired  
  25.   
  26.             String oldValueStr = jedis.getSet(lockKey, expiresStr);  
  27.             //获取上一个锁到期时间,并设置现在的锁到期时间,  
  28.             //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的  
  29.             if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {  
  30.                 //如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁  
  31.                 // lock acquired  
  32.                 locked = true;  
  33.                 return true;  
  34.             }  
  35.         }  
  36.         timeout -= 100;  
  37.         Thread.sleep(100);  
  38.     }  
  39.     return false;  
  40. }  



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

Java代码  收藏代码
  1. JedisPool pool;  
  2. JedisLock jedisLock = new JedisLock(pool.getResource(), lockKey, timeoutMsecs, expireMsecs);  
  3. try {  
  4.     if (jedisLock.acquire()) { // 启用锁  
  5.         //执行业务逻辑  
  6.     } else {  
  7.         logger.info("The time wait for lock more than [{}] ms ", timeoutMsecs);  
  8.     }  
  9. catch (Throwable t) {  
  10.     // 分布式锁异常  
  11.     logger.warn(t.getMessage(), t);  
  12. finally {  
  13.     if (jedisLock != null) {  
  14.         try {  
  15.             jedisLock.release();// 则解锁  
  16.         } catch (Exception e) {  
  17.         }  
  18.     }  
  19.     if (jedis != null) {  
  20.         try {  
  21.             pool.returnResource(jedis);// 还到连接池里  
  22.         } catch (Exception e) {  
  23.         }  
  24.     }  
  25. }  


犀利用法
用匿名类来实现,代码非常简洁
至于SimpleLock的实现,请在我附件中下载查看

Java代码  收藏代码
  1. SimpleLock lock = new SimpleLock(key);  
  2. lock.wrap(new Runnable() {  
  3.     @Override  
  4.     public void run() {  
  5.         //此处代码是锁上的  
  6.     }  
  7. });  

http://phl.iteye.com/blog/2029944

分享到:
评论

相关推荐

    springboot基于redis分布式锁

    3. **简单的API**:Redis提供了丰富的命令集,如`SETNX`、`EXPIRE`等,方便实现锁的逻辑。 要在SpringBoot中集成Redis,你需要先在项目中添加对应的依赖。SpringBoot提供了对Redis的自动配置支持,只需在`pom.xml`...

    redis分布式锁.zip

    Redis 的高可用性和低延迟特性使其成为实现分布式锁的理想选择。下面将详细介绍如何使用 Redis 实现分布式锁以及其中可能遇到的问题和解决方案。 首先,我们需要理解分布式锁的基本概念。分布式锁是一种在分布式...

    Java基于redis实现分布式锁代码实例

    通过使用 Redis 的 setnx 命令和 expire 命令,可以实现分布式锁的功能。但是,需要注意的是,在实现分布式锁时,需要考虑到各种可能的异常情况,例如服务器挂掉等,并且需要对分布式锁进行优化,例如加过期时间,...

    redis分布式锁工具包,提供纯Java方式调用,支持传统Spring工程.zip

    Redis 分布式锁工具包是为了解决在分布式系统中实现锁的问题,它提供了一种纯Java的方式来调用,使得在传统的Spring工程中可以轻松集成和使用。在现代的高并发、分布式环境下,单机锁已经无法满足需求,因为它们不能...

    redis实现分布式锁,自旋式加锁,lua原子性解锁

    Redis中的分布式锁实现通常基于`SETNX`命令或`SET`命令的`nx`与`ex`组合。`SETNX`命令用于设置键值,但如果键已经存在,则不执行任何操作,这可以确保锁的互斥性。`SET key value EX timeout NX`则同时设置了超时...

    用Redis实现分布式锁_redis_分布式_

    2. Redlock:由Redis作者Antirez提出的分布式锁算法,通过在多台Redis实例上实现锁来提高可用性和容错性。 综上所述,Redis是实现分布式锁的理想选择,其高效、原子性的特性使得在分布式系统中控制共享资源成为可能...

    redis-distributed-lock-starter.rar

    描述中提到的“开箱即用”意味着这个starter已经将所有必要的配置和实现封装好,开发者只需引入依赖,就能在自己的应用中使用Redis分布式锁。此外,“根据方法时间设定方法解锁的最优时间”表明该库可能具有智能锁...

    Redis分布式锁的实现方式(redis面试题)

    分布式锁是一种在分布式系统中实现锁的机制,用于协调分布在不同节点上的进程对共享资源的访问。在单机系统中,我们通常使用线程锁(如Java中的`synchronized`关键字或`java.util.concurrent.locks.Lock`接口)来...

    基于Redis方式实现分布式锁

    一旦键过期,Redis会自动删除该键值对,从而实现锁的自动释放,避免死锁。 3. **`DEL` 命令**:用于删除Redis中的键,释放锁。 ##### 实现步骤 1. **获取锁**: - 使用`SETNX`命令尝试获取锁,并使用`EXPIRE`命令...

    redis分布式锁实现代码

    使用命令介绍: SETNX SETNX key val 当且仅当key不存在时,set一个key为val的字符串,返回1;...为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。 delete delete key 删除key

    Redis分布式锁存在的问题及解决方案(值得珍藏)

    Redis分布式锁是解决多节点集群部署中并发控制的重要工具,但在使用过程中,它存在一些问题,需要谨慎处理。本文将详细探讨这些问题及其解决方案。 首先,死锁问题是Redis分布式锁的一个常见挑战。当一个进程获取了...

    Go-用Redis实现分布式锁与实现任务队列

    本篇文章将深入探讨如何利用Redis实现分布式锁以及如何构建一个基于Redis的任务队列。 分布式锁是解决多节点共享资源时防止数据冲突的关键机制。在Go中,我们通常通过与Redis交互来实现这一功能。Redis提供了`SETNX...

    003 redis分布式锁 jedis分布式锁 Redisson分布式锁 分段锁

    总的来说,Redis分布式锁通过Jedis或Redisson提供了可靠、高效的并发控制方案。分段锁作为一种优化策略,可以进一步提升大规模并发环境下的系统性能。在实际项目中,应根据业务需求和性能指标选择合适的锁实现,以...

    Redis高并发分布式锁-1031.docx

    首先,Redis分布式锁的基本原理是利用`SETNX`(Set if Not eXists)命令来尝试获取锁。当一个客户端尝试设置一个键值时,如果该键不存在,那么键值对会被设置并返回`true`,否则返回`false`,表示锁已被其他客户端...

    redis分布式锁实现

    分布式锁是一种在分布式系统中实现锁机制的方法,它允许多个节点在共享资源上进行互斥访问。在微服务架构和大型互联网应用中,由于服务之间的数据一致性与并发控制的需求,分布式锁扮演着至关重要的角色。本篇文章将...

    Tedu五阶段Redis分布式锁

    Tedu五阶段Redis分布式锁正是利用了Redis的这些特性,通过`SETNX`等命令来实现分布式环境下的锁控制。这种机制有效地解决了多线程或多进程环境下资源的竞争问题,保证了系统的稳定性和一致性。 在实际应用中,还...

    redis-distributed-lock:Python3的redis分布式锁,使用setnx和lua脚本,提供块和无块函数

    Python3的redis分布式锁,使用setnx和lua脚本,提供块和无块函数 用法 import redis from .redis_lock import RedisLock redis_client = redis.Redis(host="127.0.0.1", port=6379, db=0) lock = RedisLock(redis_...

Global site tag (gtag.js) - Google Analytics