NOTE

2.5 Redis Distributed Locks

1. What is a Redis distributed lock - A distributed lock implemented based on Redis 2. Redis distributed lock implementation 2.1. Single instance 2.1.1. Locking - Set a key when it does not exist - SET NX guarantees atomicity - The key guarantees mutual exclusion - The value guarantees that locking and unlocking are performed by the same client

Redis / CacheCreated Updated 3 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. What Is a Redis Distributed Lock?

A distributed lock implemented based on Redis.

2. Redis Distributed Lock Implementation

2.1. Single Instance

2.1.1. Locking

  • Set a key when it does not exist.
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * Try to acquire the distributed lock.
     * @param jedis Redis client
     * @param lockKey lock
     * @param requestId request identifier
     * @param expireTime expiration time
     * @return whether acquisition succeeded
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
  • SET NX is used to guarantee atomicity.
  • The key is used to guarantee mutual exclusion.
  • The value is used to guarantee that locking and unlocking must be performed by the same client.
  • PX and expireTime are used to guarantee timeout-based release.

2.1.2. Unlocking

  • When the key exists, delete it only when the value is the same.
public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * Release the distributed lock.
     * @param jedis Redis client
     * @param lockKey lock
     * @param requestId request identifier
     * @return whether release succeeded
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
  • Use a Lua script to guarantee atomicity.
  • The key is used to guarantee mutual exclusion.
  • The value is used to guarantee that locking and unlocking must be performed by the same client.

2.2. Multiple Instances

Assume there are N completely independent Redis master instances (note that this is not Redis Cluster).

2.2.1. Redlock Algorithm

  1. Get the current time in milliseconds.
  2. Try to acquire the lock on all N instances in sequence, using the same key and random value on every instance.
    • When setting the lock on each instance, the client uses a timeout smaller than the total lock auto-release time.
      • For example, if the auto-release time is 10 seconds, the timeout may be in the range of about 5–50 ms. This prevents the client from staying blocked for a long time while trying to communicate with a Redis node that is down. If an instance is unavailable, we should try the next instance as soon as possible.
  3. The client calculates the elapsed time for acquiring the lock by subtracting the timestamp obtained in step 1 from the current time. If and only if the client can acquire the lock on a majority of instances (at least 3 when N=5), and the total time used to acquire the lock is less than the lock validity time, the lock is considered acquired.
  4. If the lock is acquired, its validity time is considered to be the initial validity time minus the elapsed time calculated in step 3.
  5. If the client cannot acquire the lock for some reason (either it cannot lock N/2+1 instances, or the validity time is negative), it tries to unlock all instances, including instances it believes it failed to lock.

2.2.2. Problems with Distributed Locks on Redis Clusters

2.2.2.1. Consistency Problem
  • Redis replication is asynchronous. If the master fails and the lock data has not been replicated to the slave, the lock is lost during failover.
2.2.2.2. Timeout
  • Node A and node B compete for a distributed lock. Node A acquires it successfully and sets the timeout to 10 seconds. If node A’s business logic takes more than 10 seconds, the lock is released and can then be acquired by node B.
  • Solution:
    • Renewal: every certain amount of time, node A asks the Redis server to extend the distributed lock.
    • Problem: if the application is written in a language with GC such as Java, a stop-the-world event may occur while node A is renewing, preventing renewal; the lock may still be acquired by node B.
      • Solution: under the CAP trade-off, this problem cannot be completely eliminated. Timeout is introduced for availability, while renewal is introduced for consistency, so one option is to give up the distributed lock and use optimistic locking instead.

3. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub