/**
* @description: Redis 分布式锁
* @author: cy
* @create: 2020-07-10
**/
@Slf4j
@Component
public class RedisLock {
private final RedisTemplate<String, Object> redisTemplate;
private final ThreadLocal<String> localKeys = new ThreadLocal<>();
private final ThreadLocal<String> localValues = new ThreadLocal<>();
private final static String LOCK_PREFIX = "REDIS_LOCK:";
private final static long LOCK_SUCCESS = 1;
private final static long RELEASE_SUCCESS = 1;
private final static long LOCK_EXPIRED = -1;
@Value("${application.lock-timeout}")
private long timeout;
//定义获取锁的lua脚本
private final static DefaultRedisScript<Long> LOCK_LUA_SCRIPT = new DefaultRedisScript<>(
"if redis.call(\"setnx\", KEYS[1], KEYS[2]) == 1 then return redis.call(\"pexpire\", KEYS[1], KEYS[3]) else return 0 end"
, Long.class
);
//定义释放锁的lua脚本
private final static DefaultRedisScript<Long> UNLOCK_LUA_SCRIPT = new DefaultRedisScript<>(
"if redis.call(\"get\",KEYS[1]) == KEYS[2] then return redis.call(\"del\",KEYS[1]) else return -1 end"
, Long.class
);
public RedisLock(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 清除本地线程变量,防止内存泄露
*/
private void clean() {
localValues.remove();
localKeys.remove();
}
/**
* 获取RedisKey
* @param key 原始KEY,如果为空,自动生成随机KEY
* @return
*/
private String getRedisKey(String key) {
//如果Key为空且线程已经保存,直接用,异常保护
if (StringUtils.isEmpty(key) && !StringUtils.isEmpty(localKeys.get())) {
return localKeys.get();
}
//如果都是空那就抛出异常
if (StringUtils.isEmpty(key) && StringUtils.isEmpty(localKeys.get())) {
throw new RuntimeException("key is null");
}
return LOCK_PREFIX + key;
}
public boolean lock(String key, String value) {
return lock(key, value, timeout);
}
/**
* 加锁
* @param key Key
* @param timeout 过期时间
* @return
*/
public boolean lock(String key, String value, long timeout) {
try {
long start = System.currentTimeMillis();
final String redisKey = this.getRedisKey(key);
//组装lua脚本参数
List<String> keys = Arrays.asList(redisKey, value, String.valueOf(timeout));
//执行脚本
Long result = redisTemplate.execute(LOCK_LUA_SCRIPT, keys);
//存储本地变量
if (result != null && LOCK_SUCCESS == result) {
localValues.set(value);
localKeys.set(redisKey);
log.info("成功获取锁:" + Thread.currentThread().getName() + ", Status code:" + result);
return true;
} else {
//重试获取锁
log.debug("开始重试获取锁:" + Thread.currentThread().getName() + ", Status code reply:" + result);
while(true) {
try {
//检测是否超时
if (System.currentTimeMillis() - start > timeout) {
return false;
}
//休眠一定时间后再获取锁,这里时间可以通过外部设置
Thread.sleep(20);
result = redisTemplate.execute(LOCK_LUA_SCRIPT, keys);
if(!StringUtils.isEmpty(result) && result == LOCK_SUCCESS) {
localValues.set(value);
localKeys.set(redisKey);
log.info("成功获取锁:" + Thread.currentThread().getName() + ", Status code:" + result);
return true;
}
} catch (Exception e) {
log.error("获取锁异常:" + Thread.currentThread().getName(), e);
break;
}
}
}
} catch (Exception e1) {
log.error("获取锁异常:" + Thread.currentThread().getName(), e1);
}
return false;
}
/**
* 释放KEY
* @param key
* @return
*/
public boolean unlock(String key) {
try {
String localKey = localKeys.get();
//如果本地线程没有KEY,说明还没加锁,不能释放
if(StringUtils.isEmpty(localKey)) {
log.error("释放锁失败: lock key not found");
return false;
}
String redisKey = getRedisKey(key);
//判断KEY是否正确,不能释放其他线程的KEY
if(!StringUtils.isEmpty(localKey) && !localKey.equals(redisKey)) {
log.error("释放锁失败: illegal key:" + key);
return false;
}
//组装lua脚本参数
List<String> keys = Arrays.asList(redisKey, localValues.get());
// 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
Long result = redisTemplate.execute(UNLOCK_LUA_SCRIPT, keys);
//如果这里抛异常,后续锁无法释放
if (result != null && RELEASE_SUCCESS == result) {
log.info("释放锁成功:" + Thread.currentThread().getName() + ", Status code reply=" + result);
return true;
} else if (!StringUtils.isEmpty(result) && result == LOCK_EXPIRED) {
//返回-1说明获取到的KEY值与requestId不一致或者KEY不存在,可能已经过期或被其他线程加锁
// 一般发生在key的过期时间短于业务处理时间,属于正常可接受情况
log.warn("释放锁异常:" + Thread.currentThread().getName() + ", key has expired or released. Status code reply=" + result);
} else {
//其他情况,一般是删除KEY失败,返回0
log.error("释放锁失败:" + Thread.currentThread().getName() + ", del key failed. Status code reply=" + result);
}
} catch (Exception e) {
log.error("释放锁失败异常", e);
} finally {
//清除本地变量
this.clean();
}
return false;
}
}
redis实现分布式锁 工具类
最新推荐文章于 2025-04-10 22:51:40 发布