Design ideas of redis distributed lock
-
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.
-
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(); } }