The redis lock mechanism realizes that the timed task is executed only once

Redis has a series of commands that end with NX, which is the abbreviation of Not eXists. For example, the SETNX command should be understood as: SET if Not eXists. This series of commands is very useful, and here we talk about using SETNX to implement distributed locks. 

Implementing Distributed Locks with SETNX Using SETNX to implement distributed locks is very simple. For example: a client wants to acquire a lock named foo, the client acquires it with the following command:  SETNX lock.foo <current Unix time + lock timeout + 1>  


  • If it returns 1, the client acquires the lock, and setting the key value of lock.foo to the time value indicates that the key has been locked, and the client can finally release the lock through DEL lock.foo.

  • If it returns 0, it means that the lock has been acquired by other clients. At this time, we can return or retry and wait for the other party to complete or wait for the lock to time out.



There is a problem with the locking logic above deadlocks : what if a client holding the lock fails or crashes and cannot release the lock? We can judge whether this happens by the timestamp corresponding to the key of the lock. If the current time is greater than the value of lock.foo, the lock has expired and can be reused. When this happens, you can't simply delete the lock through DEL, and then SETNX again. When multiple clients detect that the lock has timed out, they will try to release it. There may be a race condition here, let's simulate it. This scenario: The  C0 operation timed out, but it still holds the lock, C1 and C2 read lock.foo to check the timestamp, and found that it timed out. C1 sends DEL lock.foo  C1 sends SETNX lock.foo and it succeeds. C2 sends DEL lock.foo  C2 sends SETNX lock.foo and it succeeds. In this way, both C1 and C2 get the lock! Big problem! Fortunately, this kind of problem can be avoided. Let's see how the client C3 does:  C3 sends SETNX lock.foo to acquire the lock. Since C0 still holds the lock, Redis returns a 0 to  C3. C3 sends GET lock.foo to check if the lock has timed out, if not, wait or retry. On the other hand, if it has timed out, C3 tries to acquire the lock through the following operations:  GETSET lock.foo <current Unix time + lock timeout + 1>  Through GETSET, if the timestamp obtained by C3 is still timed out, it means that C3 Got the lock as desired.  


















If before C3, a client named C4 performs the above operation one step faster than C3, then the timestamp obtained by C3 is a value that has not timed out. At this time, C3 does not obtain the lock as scheduled, and needs to wait or retry again. . Note that although C3 didn't get the lock, it overwrote the lock timeout value set by C4, but the impact of this very small error is negligible. 

Note: In order to make the distributed lock algorithm more stable, the client holding the lock should check again whether its lock has timed out before unlocking, and then do the DEL operation, because the client may be due to a time-consuming The operation is suspended. When the operation is completed, the lock has been acquired by others because of the timeout, so there is no need to unlock it. 

Sample pseudo code Based on the above code, I wrote a small piece of Fake code to describe the whole process of using distributed lock:  # 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() {
                //此处代码是锁上的
            }
        });


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325897950&siteId=291194637