28 Practical Distributed Lock Detailed Explanation and Code

分布式锁的实现 #

// 设置分布式锁
Boolean result = jedis.set(key, value, "NX", "EX", expireTime);
if (result) {
    // 获取锁成功,进行业务处理...
    // 释放锁
    jedis.del(key);
} else {
    // 获取锁失败,进行重试或其他策略...
}

在上述代码中,我们使用 Redis 的 set 命令来设置分布式锁,并设置了 NX (SET IF NOT EXISTS) 和 EX (EXPIRE) 参数来保证原子性和设置超时时间。如果设置锁成功,则可以进行业务处理,并在完成后释放锁;如果设置锁失败,则根据需求进行重试或其他策略。

总结 #

分布式锁是一种用于在分布式环境下实现并发控制的机制。Redis 分布式锁是常见的分布式锁实现之一,使用 set 命令配合 NXEX 参数可以有效地实现分布式锁。然而,需要注意处理执行超时问题和锁被误删的情况,以保证分布式锁的正确使用。 if(xxx.equals(xxx)){ // Determine if it is your own lock del(luck); // Delete the lock }

Because the check code and delete code are not atomic, they cannot be used in this way. In this case, you can use Lua scripts to execute the check and delete operations, as multiple Lua commands can ensure atomicity. The Java implementation code is as follows:

/**
 * Release distributed lock
 * @param jedis Redis client
 * @param lockKey key of the lock
 * @param flagId lock ownership identifier
 * @return whether the lock is released successfully
 */
public static boolean unLock(Jedis jedis, String lockKey, String flagId) {
    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(flagId));
    if ("1L".equals(result)) { // Check the execution result
        return true;
    }
    return false;
}

Among them, the function of the Collections.singletonList() method is to convert a String to a List, because the types of the last two parameters of jedis.eval() must be List.

Now that we have discussed the solution to the problem of mistakenly deleting the lock, let’s go back and see how to solve the problem of execution timeout. The problem of execution timeout can be solved from the following two aspects:

  1. Do not put time-consuming tasks in the locked method, and control the execution time of the method as much as possible;
  2. The maximum timeout time can be set to a longer value. Under normal circumstances, the lock will be manually deleted after it is used up, so setting a longer maximum timeout time is also feasible.

Code demonstration #

Next, let’s use Java code to implement distributed locks. The code is as follows:

import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import utils.JedisUtils;

import java.util.Collections;
public class LockExample {
    static final String _LOCKKEY = "REDISLOCK"; // Lock key
    static final String _FLAGID = "UUID:6379";  // Identifier (UUID)
    static final Integer _TimeOut = 90;     // Maximum timeout

    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        // Lock
        boolean lockResult = lock(jedis, _LOCKKEY, _FLAGID, _TimeOut);
        // Logical business processing
        if (lockResult) {
            System.out.println("Lock acquired successfully");
        } else {
            System.out.println("Failed to acquire lock");
        }
        // Manually release the lock
        if (unLock(jedis, _LOCKKEY, _FLAGID)) {
            System.out.println("Lock released successfully");
        } else {
            System.out.println("Lock release failed");
        }
    }
    /**
     * @param jedis Redis client
     * @param key Lock name
     * @param flagId Lock identifier (lock value), used to identify the ownership of the lock
     * @param secondsTime Maximum timeout time
     * @return
     */
    public static boolean lock(Jedis jedis, String key, String flagId, Integer secondsTime) {
        SetParams params = new SetParams();
        params.ex(secondsTime);
        params.nx();
        String res = jedis.set(key, flagId, params);
        if (StringUtils.isNotBlank(res) && res.equals("OK"))
            return true;
        return false;
    }
    /**
     * Release distributed lock
     * @param jedis Redis client
     * @param lockKey key of the lock
     * @param flagId lock ownership identifier
     * @return whether the lock is released successfully
     */
    public static boolean unLock(Jedis jedis, String lockKey, String flagId) {
        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(flagId));
        if ("1L".equals(result)) { // Check the execution result
            return true;
        }
        return false;
    }
}

The execution result of the above code is as follows:

Lock acquired successfully
Lock released successfully

Summary #

This article introduces the concept of locks and distributed locks. Locks are used to ensure that only one program can operate a resource at a time, to ensure the normal execution of programs during concurrency. Using Redis to implement distributed locks cannot use the setnx command because it may cause deadlocks. Therefore, we can use the multi-parameter set command introduced in Redis 2.6.12 to apply for locks. However, when using it, we should also pay attention to the execution time of the business process within the lock, which cannot be longer than the maximum timeout set by the lock, otherwise it will cause thread safety issues and mistakenly delete the lock.