单Redis实例实现分布式锁
参考:
Redis 中文官方文档:http://www.redis.cn/topics/distlock.html
存在竞态
安全和活性失效保障
按照我们的思路和设计方案,算法只需具备3个特性就可以实现一个最低保障的分布式锁。
-
安全属性(Safety property): 独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。
-
活性A(Liveness property A): 无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。
-
活性B(Liveness property B): 容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁
获取锁使用命令:
SET resource_name my_random_value NX PX 30000
key 要全局唯一,而value 要是随机字符串,可以使用UUID(可能存在相同,最好使用自旋锁生成),因为后面要判断value是否是自己的value,来删除key
删除锁
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis Lettuce 模式 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置 redis
# 配置redis
spring.redis.host=47.107.243.178
spring.redis.port=6379
#spring.redis.password=123456
spring.redis.database=0
spring.redis.timeout=3000ms
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=3000ms
# 连接池中的最大空闲连接(负数没有限制)
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
2、编写 RedisLock
/**
* redis 单体 分布式锁
*/
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 加锁
* */
public boolean tryLock(String key, String value) {
int expirationTIME = 10;
Boolean isLocked = stringRedisTemplate.opsForValue().
setIfAbsent(key, value, expirationTIME, TimeUnit.SECONDS);
if (isLocked == null) {
return false;
}
return isLocked;
}
public boolean tryLock(String key, String value,int expirationTime,TimeUnit timeUnit) {
Boolean isLocked = stringRedisTemplate.opsForValue().
setIfAbsent(key, value, expirationTime, timeUnit);
if (isLocked == null) {
return false;
}
return isLocked;
}
/**
* 解锁
* */
public Boolean unLock(String key, String value) {
// 执行 lua 脚本
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 指定 lua 脚本
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/unLock.lua")));
// 指定返回类型
redisScript.setResultType(Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);
return result != null && result > 0;
}
}
3、注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedLock {
//调用接口的名称
String key();
// 过期时间
int expiration() default 10;
// 时间类型
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
4、AOP 配置redis 分布式锁
/**
* redis 分布式锁 aop
* */
@Aspect
@Slf4j
@Component
public class RedisLockAspect {
@Autowired
RedisLock redisLock;
/**
* 配置切入点
*/
@Pointcut("@annotation(cn.patrick.adminpatrick.admin.annotation.RedLock)")
public void redisLockPointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
@Around("redisLockPointcut()")
public Object redisLockAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
// 获取注解上的值
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RedLock redLockAnnotation = method.getAnnotation(RedLock.class);
String value = UUID.randomUUID().toString().replace("-", "");
String key = redLockAnnotation.key();
int expiration = redLockAnnotation.Expiration();
Random random = new Random();
try {
// 自旋锁
while (true) {
if (redisLock.tryLock(key,value,expiration,TimeUnit.SECONDS)) {
//log.info("[{}]成功获取锁", key);
break;
}
//log.info("[{}]获取锁失败",key);
int sleepTime = random.nextInt(500);
Thread.sleep(sleepTime);
}
result = joinPoint.proceed();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
redisLock.unLock(key,value);
}
return result;
}
}