基于redis setnx的简易分布式锁

(此文章实现有误,修正版地址 : https://blog.csdn.net/qq315737546/article/details/79728676)

锁的原理, 就是设置个flag,记录下是否正被使用,可重入锁再判断下是否是自己在使用.

这个flag,必须保证所有资源用的是同一个.


synchronized关键字,lock类等, 可以保证此flag在单个jvm中唯一, 但是有多个jvm(集群,分布式)时候,就没办法保证了.

这时候需要用一个 跨jvm唯一的flag.  也就是常说的分布式锁了.


分布式锁的实现也有很多种. 基于redis,zookeeper等等, 也可以基于数据库.

基于redis的,也有setnx,incr等操作的.

本着简单,实用的原则, 写了一个简单的类,主要支持以下功能:


1.所有加锁操作都需要有时间限制,不能无限锁定

2.提供获取失败重试机制

3.释放锁时,保证释放的锁是自己获取到的


下面是代码,用到了jedis的jar, redis的连接是和spring整合配置的.这里就不列了. 所以不能直接运行

重点是LockUtil类.(锁存在,检查锁的剩余时间,然后重试,这里没有扣减此过程消耗的时间.所以极端情况会出现超出tryLock的timeOut设置而获取到锁)



import java.util.Objects;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.zhidian.common.util.SpringContextUtil;

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

/**
 * 基于redis setnx的 分布式锁 实, 前提是所有的锁都要有锁定时间.
 * 获取锁的时候,需要指定value,在unlock的时候,会根据value判断是否remove
 * 
 * @author: qq315737546
 */
public class LockUtil {
	private static Log logger = LogFactory.getLog(LockUtil.class);
	private static final String LOCK_PREFIX = "LOCK";
	private static final Integer DEFAULT_LOCK_TIME = 20;// 默认锁定时间秒
	private static final Long DEFAULT_SLEEP_TIME = 100L;// 默认sleep时间,100毫秒

	/**
	 * 单台服务器可直接用系统时间,多台服务器可用redis.time()
	 * 
	 * @return
	 * @Author: qq315737546
	 */
	public static String getCurTime() {
		return String.valueOf(System.currentTimeMillis());
	}

	/**
	 * 获取锁,如果失败,自动重试
	 * 
	 * @param key
	 * @param value
	 * @return
	 * @Author: qq315737546
	 */
	public static void lock(String key, String value) {
		lock(key, value, DEFAULT_LOCK_TIME);
	}

	/**
	 * 获取锁,如果失败,自动重试
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            锁定时间
	 * @return
	 * @Author: qq315737546
	 */
	public static void lock(String key, String value, int lockTime) {
		lock(key, value, lockTime, true);
	}

	/**
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            锁定时间
	 * @param reTry
	 *            是否重试
	 * @return
	 * @Author: wangxingfei
	 */
	private static boolean lock(String key, String value, int lockTime, boolean reTry) {
		return lock(key, value, lockTime, reTry, 0, false, 0);
	}

	/**
	 * 获取锁,如果失败,直接返回false
	 * 
	 * @param key
	 * @param value
	 * @return
	 * @Author: qq315737546
	 */
	public static boolean tryLock(String key, String value) {
		return tryLock(key, value, DEFAULT_LOCK_TIME);
	}

	/**
	 * 获取锁,如果失败,直接返回false
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            锁定时间
	 * @return
	 * @Author: qq315737546
	 */
	public static boolean tryLock(String key, String value, int lockTime) {
		return lock(key, value, lockTime, false);
	}

	/**
	 * 尝试获取锁,如果获取失败,重试,直到成功或超出指定时间
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 * @param timeOutMillis
	 *            获取锁超时时间 (毫秒)
	 * 
	 * @return
	 * @Author: qq315737546
	 */
	public static boolean tryLock(String key, String value, int lockTime, long timeOutMillis) {
		return lock(key, value, lockTime, true, 0, true, timeOutMillis);
	}

	/**
	 * 释放锁,key对应的value于参数value一致,才删除key
	 * 
	 * @param key
	 * @param value
	 * @Author: qq315737546
	 */
	public static void unlock(String key, String value) {
		String fullKey = getFullKey(key);
		String existValue = JedisUtil.getObject(fullKey);
		if (Objects.equals(value, existValue)) {
			logger.info("unlock success ; key:" + key + ",value:" + value);
			JedisUtil.remove(fullKey);
		} else {
			logger.info("unlock failed ; key:" + key + ",value:" + value + ",existValue:" + existValue);
		}
	}

	/**
	 * 获取锁
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            锁定时间
	 * @param reTry
	 *            失败是否重试
	 * @param curTryTime
	 *            当前尝试次数
	 * @param needTimeOut
	 *            是否需要判断超时时间
	 * @param timeOutMillis
	 *            尝试超时时间(毫秒)
	 * @return
	 * @Author: qq315737546
	 */
	private static boolean lock(String key, String value, int lockTime, boolean reTry, int curTryTime,
			boolean needTimeOut, long timeOutMillis) {
		logger.info(Thread.currentThread().getName() + ",lock come in ; key:" + key + ",value:" + value + ",lockTime:"
				+ lockTime + ",reTry:" + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut
				+ ",timeOutMillis:" + timeOutMillis);
		curTryTime++;
		String fullKey = getFullKey(key);
		long result = JedisUtil.setnx(fullKey, value);
		// 获取成功,直接返回
		if (result > 0) {
			logger.info("lock success ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
			JedisUtil.expire(fullKey, lockTime);
			return true;
		}

		// 如果不成功,判断现在的key的有效期,如果是无限期,则删除key,并重试
		long expire = JedisUtil.ttl(fullKey);
		if (expire < 0) {
			if (expire == -1) {
				JedisUtil.remove(fullKey);
				logger.info("remove key ; key:" + key + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut
						+ ",timeOutMillis:" + timeOutMillis);
			} else {
				logger.info("ttl key ; key:" + key + " is return " + expire);
			}
			return lock(key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
		}

		// 获取失败,不需要重试,直接返回
		if (!reTry) {
			logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
			return false;
		}

		// 获取失败, 且已超时,返回
		if (needTimeOut && timeOutMillis <= 0) {
			logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
			return false;
		}

		// 获取sleep时间
		long sleepMillis = DEFAULT_SLEEP_TIME;
		if (needTimeOut) {
			timeOutMillis = timeOutMillis - DEFAULT_SLEEP_TIME;
			if (timeOutMillis < DEFAULT_SLEEP_TIME) {
				sleepMillis = timeOutMillis;
			}
		}

		// sleep后重新获取锁
		try {
			Thread.sleep(sleepMillis);
		} catch (InterruptedException e) {
			logger.error("lock sleep errro ; key:" + key + ",value:" + value, e);
		}

		if (curTryTime > 100) {
			logger.warn("lock warning ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
		}

		return lock(key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
	}

	private static String getFullKey(String key) {
		return LOCK_PREFIX + ":" + key;
	}

}

class JedisUtil {

	private static Log logger = LogFactory.getLog(JedisUtil.class);

	protected static JedisPool jedisPool = (JedisPool) SpringContextUtil.getBean("jedisPool");;

	/**
	 * 获取redis操作实例(不必加锁)
	 * 
	 * @return jedis
	 */
	protected static Jedis getJedis() throws JedisException {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
		} catch (JedisException e) {
			logger.warn("failed:jedisPool getResource.", e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw e;
		}
		return jedis;
	}

	protected static void release(Jedis jedis, boolean isBroken) {
		if (jedis != null) {
			if (isBroken) {
				jedisPool.returnBrokenResource(jedis);
			} else {
				jedisPool.returnResource(jedis);
			}
		}
	}

	/**
	 * set if not exists
	 */
	public static long setnx(String key, String value) {
		Jedis jedis = null;
		boolean isBroken = false;
		long result = 0L;
		try {
			jedis = getJedis();
			result = jedis.setnx(key, value);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao::setnx: key: " + key + ",value:" + value + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
		return result;
	}

	/**
	 * 设置过期时间
	 */
	public static void expire(String fullKey, int lockTime) {
		Jedis jedis = null;
		boolean isBroken = false;
		try {
			jedis = getJedis();
			jedis.expire(fullKey, lockTime);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao:expire: key: " + fullKey + ",lockTime:" + lockTime + " message: " + e.getMessage(),e);
		} finally {
			release(jedis, isBroken);
		}
	}

	/**
	 * 查看key的有效期
	 * 
	 * @param fullKey
	 * @return 如果key不存在或者已过期,返回 -2 ;如果key没有设置过期时间(永久有效),返回 -1 ,否则返回有效期(单位秒)
	 */
	public static long ttl(String fullKey) {
		Jedis jedis = null;
		boolean isBroken = false;
		Long result = 0L;
		try {
			jedis = getJedis();
			result = jedis.ttl(fullKey);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao: ttl : key: " + fullKey + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
		return result;
	}

	public static void remove(String key) {
		Jedis jedis = null;
		boolean isBroken = false;
		try {
			jedis = getJedis();
			jedis.del(key);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao::remove: key: " + key + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
	}
	
	public static String getObject(String key) {
		Jedis jedis = null;
		boolean isBroken = false;
		try {
			jedis = getJedis();
			String value = jedis.get(key);
			return value;
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao::getObject: key: " + key + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
		return null;

	}

}


使用示例如下

		String key = "testLock";
		String value = LockUtil.getCurTime();
		
		try{
			LockUtil.lock(key, value);
		}finally{
			LockUtil.unlock(key, value);
		}
		
		boolean lock = LockUtil.tryLock(key, value);
		if(lock){
			try{
				//TODO 
			}finally{
				LockUtil.unlock(key, value);
			}
		}


欢迎讨论拍砖交流


你可能感兴趣的:(基于redis setnx的简易分布式锁)