本文是一篇简单封装redis做分布式锁的工具类文章,不涉及原理,源码,不适用于redis集群!
// 利用jedis实现
@Slf4j
public class RedisLockUtil {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* @description: 获取redis锁
* @param: [jedis, lockKey, lockValue, expire]
* @return: boolean
* @author: apollo
* @date: 2019-10-17
*/
public static boolean tryLock(Jedis jedis, String lockKey, String lockValue, int expire) {
try {
String result = jedis.set(lockKey, lockValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire);
return Objects.equals(result, LOCK_SUCCESS);
} catch (Exception e) {
log.error("获取redis锁失败,错误信息: ", e);
return false;
} finally {
//释放连接
close(jedis);
}
}
/**
* @description: 释放redis锁(使用lua脚本),需要Redis服务器支持lua脚本
* @param: [lockKey, lockValue]
* @return: boolean
* @author: apollo
* @date: 2019-10-17
*/
public static boolean unLuaLock(Jedis jedis, String lockKey, String lockValue) {
try {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
return Objects.equals(result, LOCK_SUCCESS);
} catch (Exception e) {
log.error("释放redis锁失败,错误信息: ", e);
return false;
} finally {
close(jedis);
}
}
/**
* @description: 释放redis锁, 高并发的情况下可能存在风险,无法保证get指令和del指令的原子性
* @param: [lockKey, lockValue]
* @return: boolean
* @author: apollo
* @date: 2019-10-17
*/
public static boolean unLock(Jedis jedis, String lockKey, String lockValue) {
try {
String value = jedis.get(lockKey);
if (value.equals(lockValue)) {
jedis.del(lockKey);
return true;
}
log.error("释放redis锁失败,释放别人锁资源,lockKey: {}, lockValue: {} ", lockKey, lockValue);
return false;
} catch (Exception e) {
log.error("释放redis锁失败,错误信息: ", e);
return false;
} finally {
close(jedis);
}
}
private static void close(Jedis jedis) {
//释放连接
if (jedis != null) {
jedis.close();
}
}
// 利用redisTemplate实现
@Slf4j
@Component
public class RedisLockUtil {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
@Resource
private RedisTemplate<String, String> redisTemplate;
/**
* @description: 获取redis锁
* @param: [jedis, lockKey, lockValue, expireTime]
* @return: boolean
* @author: apollo
*/
public boolean tryLock(String lockKey, String lockValue, long expireTime) {
try {
String execute = redisTemplate.execute((RedisCallback<String>) connection -> {
JedisCommands jedisCommands = (JedisCommands) connection.getNativeConnection();
return jedisCommands.set(lockKey, lockValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
});
return Objects.equals(execute, LOCK_SUCCESS);
} catch (Exception e) {
log.error("获取redis锁失败,错误信息: ", e);
}
return Boolean.FALSE;
}
/**
* @description: 释放redis锁(使用lua脚本),需要Redis服务器支持lua脚本
* @param: [lockKey, lockValue]
* @return: boolean
* @author: apollo
*/
public boolean releaseLuaLock(String lockKey, String lockValue) {
try {
Long execute = redisTemplate.execute((RedisCallback<Long>) connection -> {
Object nativeConnection = connection.getNativeConnection();
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
} else if (nativeConnection instanceof Jedis) {
// 单机模式
return (Long) ((Jedis) nativeConnection).eval(SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
}
return 0L;
});
return Objects.nonNull(execute) && execute > 0;
} catch (Exception e) {
log.error("释放redis锁失败,错误信息: ", e);
}
return Boolean.FALSE;
}
/**
* @description: 释放redis锁, 高并发的情况下可能存在风险,无法保证get指令和del指令的原子性
* @param: [lockKey, lockValue]
* @return: boolean
* @author: apollo
*/
public boolean releaseLock(String lockKey, String lockValue) {
try {
String value = redisTemplate.opsForValue().get(lockKey);
if (Objects.equals(value, lockValue)) {
return Optional.ofNullable(redisTemplate.delete(lockKey)).orElse(Boolean.FALSE);
}
log.error("释放redis锁失败,释放别人锁资源,lockKey: {}, lockValue: {} ", lockKey, lockValue);
} catch (Exception e) {
log.error("释放redis锁失败,错误信息: ", e);
}
return Boolean.FALSE;
}
}
ps:大家众所周知redis是单线程模型,天然原子性!然而并不意味着redis只有一个线程,这是大错特错,redis的io多路复用模型就使用了线程池,主从同步也是fork子线程等等。redis的单线程模型可以理解为它的事件处理是单线程的,通俗一点就是,一个get指令或者是别的指令是原子性。在上述unLock()方法中,需要先get,在代码中equals,然后再del会存在风险。建议大家使用unLuaLock(), lua脚本作用就是get,equals,del, 这段脚本对于redis来说就是一个指令(事件),是天然支持原子性!