美文网首页贱指offer程序员Java学习笔记
RedisTemplate用SETNX命令实现分布式锁

RedisTemplate用SETNX命令实现分布式锁

作者: 姜小码 | 来源:发表于2017-08-11 00:03 被阅读728次

    使用SETNX命令获取分布式锁的步骤:

    • C1和C2线程同时检查时间戳获取锁,执行SETNX命令并都返回0,此时锁仍被C3持有,并且C3已经崩溃
    • C1 DEL
    • C1 使用SETNX命令获取锁,并且成功
    • C2 DEL
    • C2 使用SETNX命令获取锁,并且成功
    • ERROR : 由于竞态条件,C1和C2都获取到了锁

    幸运的是,以下面的步骤完全可以避免这种情况发生,看看C4线程如何操作

    • C4使用SETNX命令获取锁
    • C3已经崩溃但是仍然持有锁,所以Redis返回0给C4
    • C4使用GET命令获取锁并检查锁是否已经过期,如果没有过期,则继续等待一段时间并重新重试
    • 如果锁已经过期,C4尝试 GETSET lock.foo <current Unix timestamp + lock timeout + 1>
    • 利用GETSET语法,C4可以检查旧时间是否仍然是过期时间,如果是,则获取锁
    • 如果另一个客户端C5率先获取到锁,C4执行GETSET命令后将返回非过期时间,然后C4继续从头开始重新尝试获取锁。此操作C4将延长一点C5获取到的锁的过期时间,不过这不是什么大问题。
    private static String LOCK_PREFIX = "prefix";
    
    public boolean lock(String key) {
    
            String lock = LOCK_PREFIX + key;
    
            return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
    
                long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
                Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
    
    
                if (acquire) {
                    return true;
                } else {
    
                    byte[] value = connection.get(lock.getBytes());
    
                    if (Objects.nonNull(value) && value.length > 0) {
    
                        long expireTime = Long.parseLong(new String(value));
    
                        if (expireTime < System.currentTimeMillis()) {
                            byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
    
                            return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                        }
                    }
                }
                return false;
            });
        }
    

    链接:更强大的Redis分布式客户端 - Redisson

    相关文章

      网友评论

      • 方一物:“如果另一个客户端C5率先获取到锁,C4执行GETSET命令后将返回非过期时间,然后C4继续从头开始重新尝试获取锁。此操作C4将延长一点C5获取到的锁的过期时间,不过这不是什么大问题。”
        高并发问题下,该问题会被无限方法,“不是什么大问题”会变成莫名其妙令人郁闷的“大问题”
      • 方一物:最外层else那一段,有明显的逻辑bug,在多个线程抢占锁的情况下,有可能在该逻辑段进行的过程中,锁被别的线程获取成功,而当前线程接下来进行的是getset操作,别的线程的新锁有可能会被直接覆盖掉,虽然后面有旧value与当前时间的对比操作,但是能起到的作用只是保证了当前线程不会获取到“脏”锁,而“value”覆盖的操作发生在前,别的线程获取到的锁的有效时间已经“被延长了”。如果在高并发情况下,而获取到锁的线程恰好crash了,那么它本来设置的“过期时间”会被其它线程无限期的延长,那么所有等待锁的线程将会无限期等待。个人看法哈,欢迎探讨,指出不足

      本文标题:RedisTemplate用SETNX命令实现分布式锁

      本文链接:https://www.haomeiwen.com/subject/smbkrxtx.html