Redis distributed lock

Distributed locks, multiple JVMs, multiple threads, mutually exclusive access to shared resources

With codis, transactions cannot be used

Extreme scenarios: out of order, program crash, network delay, etc., the key is not released

 

 

http://www.redis.cn/commands/set.html

 

==================

package com.enterprise.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
 * Distributed lock based on REDIS
 *
 * @author chen.kuan April 29, 2016
 *
 */
public class RedisLock {
	public static final int DEFAULT_AVAILABLE_TIME = 600; // Default validity unit S

	private static final String LOCK_SUCCESS_CODE = "OK";

	private JedisPool pool;

	private final int availableTime;

	private final String redisKey;

	public RedisLock(JedisPool pool, String redisKey) {
		this(pool, DEFAULT_AVAILABLE_TIME, redisKey);
	}

	/**
	 * @param pool
	 * @param availableTime
	 * @param redisKey
	 */
	public RedisLock(JedisPool pool, int availableTime, String redisKey) {
		this.pool = pool;
		this.availableTime = availableTime;
		this.redisKey = redisKey;
	}

	/**
	 * @return true lock success false lock failure
	 */
	public boolean tryLock() {
		String code = lock();
		if (LOCK_SUCCESS_CODE.equals(code)) {
			return true;
		}
		return false;
	}

	/**
	 * Lock based on set(final String key, final String value, final String nxxx, final String expx, final int time). </br>
	 * A timeout must be set for locking to avoid deadlock problems caused by unlock failures due to JVM downtime, IO and other reasons. </br>
	 * If the business execution time is too long, the lock will be automatically released when the specified availableTime is exhausted. </br>
	 *
	 *
	 * @return status code
	 */
	private String lock() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			String code = jedis.set(redisKey, "1", "NX", "EX", availxableTime);
			return code;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			pool.returnResource(jedis);
		}
	}

	public void unlock() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			jedis.del(redisKey);
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

}

 

 

 

package com.enterprise.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
 * Distributed lock based on REDIS<br>
 *
 * @author chen.kuan April 29, 2016
 *
 */
public class RedisLock {
	public static final int DEFAULT_AVAILABLE_TIME = 600; // Default validity unit S

	private JedisPool pool;

	private final int availableTime;

	private final String redisKey;

	public RedisLock(JedisPool pool, String redisKey) {
		this(pool, DEFAULT_AVAILABLE_TIME, redisKey);
	}

	public RedisLock(JedisPool pool, int availableTime, String redisKey) {
		this.pool = pool;
		this.availableTime = availableTime;
		this.redisKey = redisKey;
	}

	/**
	 * @return true lock success false lock failure
	 */
	public boolean tryLock() {
		Long statusCode = lockAndSetExpiredTime ();
		if (statusCode == 1) {
			return true;
		}
		// lock failed
		// Need to check whether the lock timeout has occurred. The lock timeout means that the lock is not displayed and released, that is, the key-value pair in REDIS has not been deleted.
		String expectedExpiredTime = get();
		if (expectedExpiredTime != null && System.currentTimeMillis() > Long.valueOf(expectedExpiredTime)) {
			// Lock timeout: multiple JVMs, multiple threads, consistent time

			// Timeouts can be caused by:
			// 1 The code design is negligent. After the lock is obtained, the unlock will not be executed in some scenarios.
			// 2 The thread holding the lock, the business execution time is too long --> This is the worst case, because its unlock may be suddenly executed at some point in the future
			// 3 If the thread holding the lock fails to execute the unlock, the REDIS connection fail may cause this situation. In a distributed environment, the problem that a REDIS client fails to access REDIS must be considered.
			// 4 The process was killed by kill -9 before it had time to execute unlock

			// The lock timeout is detected, and the lock needs to be released to ensure that subsequent services can be locked successfully
			// This place can only ensure that the lock is deleted by setting a timeout, and the timeout must be availableTime
			// The reason is as follows:
			// 1 When this thread tries to delete the lock, the REDIS lock may have been deleted (unlock has been executed, or the setExired set by other threads has played a role), and other threads may have acquired the lock at this time
			// 2 If other threads acquire the lock, we must ensure that the competition mechanism can continue, so set the validity period to be consistent with availableTime
			if (!expired()) {
				setExpired();
			}
		}
		return false;
	}

	private Long lockAndSetExpiredTime () {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			Long reV = jedis.setnx(redisKey, (System.currentTimeMillis() + availableTime * 1000) + "");
			return reV;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			pool.returnResource(jedis);
		}
	}

	private String get() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			String value = jedis.get(redisKey);
			return value;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

	private boolean expired() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			Long ttl = jedis.pttl(redisKey); // When the key does not exist, return -2. Returns -1 when the key exists but no remaining time to live is set. Otherwise, in milliseconds, returns the remaining time to live for the key
			if (ttl != null && ttl >= 0) {
				return true;
			}
			return false;
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

	private void setExpired() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			jedis.expire(redisKey, availableTime);
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

	/**
	 * Release the lock, the thread holding the lock must call this method to release the lock after the business operation is completed
	 */
	public void unlock() {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			jedis.del(redisKey);
		} catch (Exception e) {
			if (jedis != null) {
				pool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		} finally {
			if (pool != null) {
				pool.returnResource(jedis);
			}
		}
	}

}

 

 

package com.enterprise.util;

/**
 * Based on ReidsLock, realize concurrent and mutually exclusive access to business entities
 *
 * @author chen.kuan November 13, 2015
 *
 */
public abstract class ConcurrentEntityVisitor {
	private RedisLock lock;

	public ConcurrentEntityVisitor() {
	}

	public ConcurrentEntityVisitor(RedisLock lock) {
		this.lock = lock;
	}

	public void setLock(RedisLock lock) {
		this.lock = lock;
	}

	public Result visit() {
		boolean locked = lock.tryLock();
		if (!locked) {
			return handleConflict();
		} else {
			try {
				return doVisit();
			} finally {
				lock.unlock();
			}
		}
	}

	/**
	 * In concurrent scenarios, handle conflicts when access violations are detected
	 *
	 * @return
	 */
	public abstract Result handleConflict();

	/**
	 * Access entities and do business
	 *
	 * @return
	 */
	public abstract Result doVisit();

}

 

 

 

	private RedisLock createRedisLock(String merchantUsername) {
		String key = String.format(O2O_MERCHANT_CONCURRENT_LOCK_KEY, merchantUsername);
		RedisLock lock = new RedisLock (redisUtil.getPool (), key);
		return lock;
	}

	@Override
	@Transactional(value = "transactionManager_busi", rollbackFor = Exception.class)
	public Result addScanCodePayMerchant(final String username, final String auditUserName) {
		ConcurrentEntityVisitor visit = new ConcurrentEntityVisitor(createRedisLock(username)) {
			@Override
			public Result handleConflict() {
				return Result.failed("current business" + username + "being operated");
			}

			@Override
			public Result doVisit() {
				MerchantO2OConfig config = repository.getMerchantO2OConfig(username, null);
				if (config != null) {
					return Result.failed("Current merchant" + username + "Already exist in the scan code payment list");
				}
				MerchantEntity merchantEntity = merchantBasicService.getMerchantByUserName(username);
				if (merchantEntity == null) {
					return Result.failed("Current business" + username + "does not exist");
				}
				String shopName = merchantEntity.getShopName();
				config = new MerchantO2OConfig();
				config.setAuditUserName(auditUserName);
				config.setCreateTime(new Date());
				config.setMerchantType(merchantEntity.getMerchantType());
				config.setShopName(shopName);
				config.setState(MerchantO2OConstants.SCAN_CODE_PAY_GRANTED); // state is set to Granted
				config.setUpdateTime(new Date());
				config.setUsername(username);
				config.setUserId(merchantEntity.getUserId());
				repository.addScanCodePayMerchant(config);

				supportUsingRedis(username);
				return Result.success();
			}
		};
		return visit.visit();
	}

 

 

 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326977513&siteId=291194637