Redis distributed lock design ideas + code demo implementation

Design ideas of redis distributed lock

  1. acquire lock

Use the Redis setnx command to try to acquire the lock. The setnx command can set the value of the key to the specified string if the key does not exist, and do nothing if the key already exists. Therefore, we can set the key of the lock to a unique string, and then use the setnx command to try to acquire the lock. If the acquisition is successful, it means that the current thread or process has acquired the lock.

Set the expiration time of the lock. In order to avoid the lock being occupied all the time, we need to set the expiration time of the lock. When the expiration time of the lock is up, Redis will automatically delete the lock and release the lock. When setting the expiration time of the lock, it needs to be set reasonably according to the execution time of the business logic to avoid the lock expiration time being too short or too long.

Use a unique value to identify the lock holder. In order to prevent other threads or processes from releasing the lock by mistake, we need to use a unique value to identify the lock holder. This unique value can be generated using a UUID or the ID of the current thread or process.

  1. release lock

When releasing the lock, we can use a Lua script to check whether the lock has expired, and if it expires, use the del command to release the lock. This ensures that the operation of releasing the lock is atomic, avoiding concurrency problems between checking whether the lock has expired and releasing the lock.

If the lock has expired, there is no need to release the lock to avoid accidentally releasing the lock acquired by other threads or processes. When the lock expires, other threads can acquire the lock

The following is the core code display of Redis distributed lock:

RedisUtil—connection configuration of local redis client

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
    //protected static Logger logger = Logger.getLogger(RedisUtil.class);
    
    private static String IP = "127.0.0.1";

    //Redis port number
    private static int PORT = 6379;

    //The maximum number of available connection instances, the default value is 8;
    //If the assignment value is -1, it means no limit; if the pool has allocated maxActive jedis instances, the state of the pool is exhausted (exhausted).
    private static int MAX_ACTIVE = 100;

    //Control how many idle (idle) jedis instances a pool has at most, and the default value is also 8.
    private static int MAX_IDLE = 20;

    //The maximum time to wait for an available connection, in milliseconds, the default value is -1, which means never timeout. If the waiting time is exceeded, JedisConnectionException is thrown directly;
    private static int MAX_WAIT = 3000;

    private static int TIMEOUT = 3000;

    //When borrowing a jedis instance, whether to perform the validate operation in advance; if true, the obtained jedis instances are all available;
    private static boolean TEST_ON_BORROW = true;

    //When returning to the pool, whether to perform the validate operation in advance;
    private static boolean TEST_ON_RETURN = true;

    private static JedisPool jedisPool = null;

    /**
     * redis expiration time, in seconds
     */
    public final static int EXRP_HOUR = 60 * 60; //one hour
    public final static int EXRP_DAY = 60 * 60 * 24; //one day
    public final static int EXRP_MONTH = 60 * 60 * 24 * 30; // one month

    /**
     * Initialize the Redis connection pool
     */
    private static void initialPool() {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);

            jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, null);
        } catch (Exception e) {
            //logger.error("First create JedisPool error : " + e);
            e. getMessage();
        }
    }


    /**
     * Synchronous initialization in a multi-threaded environment
     */
    private static synchronized void poolInit() {
        if (jedisPool == null) {
            initialPool();
        }
    }


    /**
     * Get the Jedis instance synchronously
     *
     * @return Jedis
     */
    public synchronized static Jedis getJedis() {
        if (jedisPool == null) {
            poolInit();
        }
        Jedis jedis = null;
        try {
            if (jedisPool != null) {
                jedis = jedisPool. getResource();
            }
        } catch (Exception e) {
            e. getMessage();
            // logger. error("Get jedis error : " + e);
        }
        return jedis;
    }


    /**
     * Release jedis resources
     *
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null & amp; & amp; jedisPool != null) {
            jedisPool. returnResource(jedis);
        }
    }

    public static Long sadd(String key, String... members) {
        Jedis jedis = null;
        Long res = null;
        try {
            jedis = getJedis();
            res = jedis. sadd(key, members);
        } catch (Exception e) {
            //logger. error("sadd error : " + e);
            e. getMessage();
        }
        return res;
    }
}

The core code for acquiring and releasing locks

import redis.clients.jedis.Jedis;

import java.util.UUID;

public class MayiktRedisLock {

    private static int lockSuccess = 1;

    /**
     * @param lockKey key value created in Redis
     * @param notLockTimie try to acquire lock timeout
     * @return return lock success value
     */
    public String getLock(String lockKey, int notLockTimie, int timeOut) {
        //Get Redis connection
        Jedis jedis = RedisUtil. getJedis();
        // Calculate the timeout when we try to acquire the lock
        Long endTime = System.currentTimeMillis() + notLockTimie;
        // The current system time is less than endTime, indicating that the acquisition lock has not timed out. Continue to loop, otherwise exit the loop
        while (System. currentTimeMillis() < endTime) {
            String lockValue = UUID. randomUUID(). toString();
            // When multiple different jvms create the same rediskey at the same time, whoever can create it successfully can acquire the lock
            if (jedis.setnx(lockKey, lockValue) == lockSuccess) {
                // add expiry date
                jedis.expire(lockKey, timeOut / 1000);
                return lockValue;
                // exit the loop
            }
            // Otherwise, continue looping
        }
        try {
            if (jedis != null) {
                jedis. close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * release lock
     *
     * @return
     */
    public boolean unLock(String locKey, String lockValue) {
        //Get Redis connection
        Jedis jedis = RedisUtil. getJedis();
        try {
            // Make sure to delete yourself when judging to acquire the lock
            if (lockValue. equals(jedis. get(locKey))) {
                return jedis.del(locKey) > 0 ? true : false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis. close();
            }
        }
        return false;
    }
}

Operation entry of RedisService demo service

import com.mayikt.utils.MayiktRedisLock;
import org.apache.commons.lang3.StringUtils;

public class RedisService {


    private static final String LOCKKEY = "mayikt_lock";

    public static void service() throws InterruptedException {
        // 1. Get the lock
        MayiktRedisLock mayiktRedisLock = new MayiktRedisLock();
        String lockValue = mayiktRedisLock.getLock(LOCKKEY, 5000, 5000);
        if (StringUtils. isEmpty(lockValue)) {
            System.out.println(Thread.currentThread().getName() + "Failed to acquire lock");
            return;
        }
        // execute our business logic
        System.out.println(Thread.currentThread().getName() + ", successfully acquired lock: lockValue:" + lockValue);
        // 3.do something --- simulate the program to perform tasks
        Thread. sleep(4000);
        //Thread. sleep(10000);

        // 4. Release the lock
        Boolean flag = mayiktRedisLock.unLock(LOCKKEY, lockValue);
        if (flag) {
            System.out.println(Thread.currentThread().getName() + ", lock released successfully: lockValue: " + lockValue);
        } else {
            System.out.println(lockValue + "The lock has expired and no longer needs to be released!");
        }
    }

    public static void main(String[] args) throws InterruptedException {

        service();
    }
}