@Component
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static Logger logger = LogManager.getLogger(RedisLock.class);
private static final String LUA_SCRIPT = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
/**
* @param supplier 需要执行的代码
* @param lockName 锁名称,redis key 名称
* @param maxLockTime 最大锁时间,maxLockTime后自动解锁,防止死锁,单位是s,传null为10s
* @param maxRetryNum 最大重试次数,到达最大重试次数后,抛出异常,可为空,为null就为5次
* @date 2021年04月07日 20:29
*
* 尝试加锁,加锁成功后执行方法,若不成功,等待500ms后递归尝试,直到maxRetryNum抛出异常
*/
public <R> R lockAndExecute(Supplier<R> supplier, String lockName, Integer maxLockTime, Integer maxRetryNum) throws BaokuRedisException {
if (Objects.isNull(maxRetryNum)) {
maxRetryNum = ObjectUtils.defaultIfNull(maxRetryNum , 5);
}
// lock的value , 脚本删除时校验 , 防止执行过程中锁过期误删
String randomValue = IdUtil.fastSimpleUUID();
// set nx ex 尝试获取锁 , 并设置过期时间 , 防止死锁
Boolean tryLockResult = redisTemplate.opsForValue().setIfAbsent(lockName, randomValue, ObjectUtils.defaultIfNull(maxLockTime , 10), TimeUnit.SECONDS);
if (Objects.nonNull(tryLockResult) && Boolean.TRUE.equals(tryLockResult)) {
R result = null;
try {
// 执行逻辑代码
result = supplier.get();
} catch (Exception e) {
logger.error("RedisLock fail", e);
} finally {
// lua脚本执行 get and del , 保证原子性
redisTemplate.execute(new DefaultRedisScript<>(LUA_SCRIPT, Long.class), Collections.singletonList(lockName), randomValue);
}
return result;
} else {
// 如果尝试次数大于 0 , 递归执行 , 否则抛出异常
if (maxRetryNum-- > 0) {
// 500ms 后重试
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
logger.error("RedisLock fail", e);
}
return lockAndExecute(supplier, lockName, maxLockTime, maxRetryNum);
} else {
throw new BaokuRedisException("the maximum number of tentatives has been reached");
}
}
}
/**
* @param lockName 锁名称,redis key 名称
* @param maxLockTime 最大锁时间,maxLockTime后自动解锁,防止死锁,单位是s,传null为10s
* @return java.lang.String
*
* 尝试加锁 , 成功返回uuid , 解锁带入此uuid
*/
public TryLockResult lock(String lockName, Integer maxLockTime){
TryLockResult result = new TryLockResult();
String randomValue = UUID.fastUUID().toString();
Boolean tryLockResult = redisTemplate.opsForValue().setIfAbsent(lockName, randomValue, ObjectUtils.defaultIfNull(maxLockTime , 10), TimeUnit.SECONDS);
if (Objects.nonNull(tryLockResult) && Boolean.TRUE.equals(tryLockResult)) {
result.setRandomValue(randomValue);
}
return result;
}
public static class TryLockResult{
private String randomValue;
public String getRandomValue() {
return randomValue;
}
public void setRandomValue(String randomValue) {
this.randomValue = randomValue;
}
public boolean isSucc(){
return StringUtils.isNotBlank(randomValue);
}
}
/**
* @param lockName 锁名称,redis key 名称
* @param randomValue lock方法返回值,用于防止误删,eg:线程A 加锁key1锁定10s,但是后续代码执行15s,线程B在第11s获取锁key1,后续代码执行10s,
* 线程A在第15s执行完成并删除锁key1,如果没有randomvalue作为各个线程的唯一值校验,那么就会删除线程B所持有的锁,导致锁被误删失效
*
* 解锁
*/
public void unlock(String lockName,String randomValue){
redisTemplate.execute(new DefaultRedisScript<>(LUA_SCRIPT, Long.class), Collections.singletonList(lockName), randomValue);
}
}
redis非集群版分布式锁
最新推荐文章于 2024-10-10 09:56:47 发布