`
jin8000608172
  • 浏览: 139568 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

redis分布式锁-SETNX实现

阅读更多
引用
http://my.oschina.net/u/1995545/blog/366381


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

    /**
     * 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 acquired
                locked = 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 acquired
                    locked = 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() {
            @Override
            public void run() {
                //此处代码是锁上的
            }
        });
分享到:
评论

相关推荐

    springboot基于redis分布式锁

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

    redis分布式锁.zip

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

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

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

    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`接口)来...

    Spring Boot Redis 实现分布式锁的方法详解.docx

    1. 锁的公平性:Spring Integration 的 RedisLockRegistry 并未实现锁的公平性,这意味着线程获取锁的顺序可能与它们请求锁的顺序不同。 2. 锁的自动续期:为了防止因网络延迟等原因导致锁意外丢失,可以设置锁自动...

    基于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