先说一次遇到的问题
关于redis分布式锁的问题,
先来一个遇到问题的代码
/**
错误示范
*/
public boolean lock(final String lockKey) {
for(String lock : locks) {
if (lock.equals(lockKey)) {
return true;
}
}
Long i = jedisClient.execute(new JedisAction<Long>() {
@Override
public Long doAction(Jedis jedis) {
//这个地方上锁
Long i = jedis.setnx(lockKey, "1");
if( i == null ) {
return 0L;
}
if(i == 1) {
//这个地方设置有效时间
jedis.expire(lockKey, LOCKKEY_EXPIRE_TIME);
locks.add(lockKey);
if(LOGGER.isInfoEnabled()) {
LOGGER.info("添加锁 [{}]",lockKey);
}
}
return i;
}
});
return i == 1;
}
以上代码的问题在于 把上锁和设置有效时间分开了,所以导致停服的时候正好上了锁,但是没有设置有效时间,所以一直有问题
那么redis应该如何正确有效的上锁呢
目前的setnx完全可以在一个方法里面实现有效时间和key
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;
/**
* 基于redis setnx的 分布式锁 实, 前提是所有的锁都要有锁定时间.
* 获取锁的时候,需要指定value,在unlock的时候,会根据value判断是否remove
*/
public class RedisLockUtil {
private static final String LOCK_PREFIX = "LOCK";
private static final Integer DEFAULT_LOCK_TIME = 10;// 默认锁定时间秒
private static final Long DEFAULT_SLEEP_TIME = 100L;// 默认sleep时间,100毫秒
private static Logger logger = LoggerFactory.getLogger(RedisLockUtil.class);
/**
* 获取缓存的value,随机值,使不同的锁value不同 (多服务器可以使用redis时间+客户端标识等)
*
* @return
*/
public static String getLockValue() {
int random = (int) ((Math.random() * 9 + 1) * 100000);
long now = System.currentTimeMillis();
return String.valueOf(now) + String.valueOf(random);
}
/**
* 获取锁,如果失败,自动重试
*
* @param key
* @param value
* @return
*/
public static boolean lock(JedisPool jedisPool, String key, String value) {
return lock(jedisPool, key, value, DEFAULT_LOCK_TIME);
}
/**
* 获取锁,如果失败,自动重试
*
* @param key
* @param value
* @param lockTime 获取成功后的锁定时间
* @return
*/
public static boolean lock(JedisPool jedisPool, String key, String value, int lockTime) {
return lock(jedisPool, key, value, lockTime, true);
}
private static boolean lock(JedisPool jedisPool, String key, String value, int lockTime, boolean reTry) {
return lock(jedisPool, key, value, lockTime, reTry, 0, false, 0);
}
/**
* 获取锁,如果失败,直接返回false
*
* @param key
* @param value
* @return
*/
public static boolean tryLock(JedisPool jedisPool, String key, String value) {
return tryLock(jedisPool, key, value, DEFAULT_LOCK_TIME);
}
/**
* 获取锁,如果失败,直接返回false
*
* @param key
* @param value
* @param lockTime 获取成功后的锁定时间
* @return
*/
public static boolean tryLock(JedisPool jedisPool, String key, String value, int lockTime) {
return lock(jedisPool, key, value, lockTime, false);
}
/**
* 尝试获取锁,如果获取失败,重试,直到成功或超出指定时间
*
* @param key
* @param value
* @param lockTime 获取成功后的锁定时间
* @param timeOut 获取锁等待超时时间
* @return
*/
public static boolean tryLock(JedisPool jedisPool, String key, String value, int lockTime, long timeOutMillis) {
return lock(jedisPool, key, value, lockTime, true, 0, true, timeOutMillis);
}
/**
* 释放锁,key对应的value于参数value一致,才删除key
*
* @param key
* @param value
*/
public static boolean unlock(JedisPool jedisPool, String key, String value) {
String fullKey = getFullKey(key);
boolean success = JedisUtil.unlock(jedisPool, fullKey, value);
if (success) {
logger.info("unlock success ; key:" + key + ",value:" + value);
} else {
logger.info("unlock failed ; key:" + key + ",value:" + value);
}
return success;
}
/**
* 获取锁
*
* @param key
* @param value
* @param lockTime 锁定时间
* @param reTry 失败是否重试
* @param curTryTime 当前尝试次数
* @param needTimeOut 是否需要判断超时时间
* @param timeOutMillis 尝试超时时间(毫秒)
* @return
*/
private static boolean lock(JedisPool jedisPool, 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);
// setnx 并设置超时时间
boolean success = JedisUtil.setnx(jedisPool, fullKey, value, (long) lockTime * 1000);
// 获取成功,直接返回
if (success) {
logger.info("lock success ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:"
+ reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:"
+ timeOutMillis);
return true;
}
// 获取失败,不需要重试,直接返回
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 = getSleepMillis(needTimeOut, timeOutMillis);
// sleep后重新获取锁
sleep(sleepMillis);
// 大于100次,打印warning日志
if (curTryTime > 100) {
logger.warn("lock warning ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:"
+ reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:"
+ timeOutMillis);
}
return lock(jedisPool, key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
}
private static long getSleepMillis(boolean needTimeOut, long timeOutMillis) {
long sleepMillis = DEFAULT_SLEEP_TIME;
if (needTimeOut) {
timeOutMillis = timeOutMillis - DEFAULT_SLEEP_TIME;
if (timeOutMillis < DEFAULT_SLEEP_TIME) {
sleepMillis = timeOutMillis;
}
}
return sleepMillis;
}
private static void sleep(long sleepMillis) {
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static String getFullKey(String key) {
return LOCK_PREFIX + ":" + key;
}
}