最近闲来时间,写了个redis-lock-stater,在pom中引入即可,直接在所要加锁的函数中使用@lock注解即可
1、首先需要了解什么是分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作,若不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。
分布式锁应具备的条件
在分布式系统环境下,一段代码在同一时间只能被一个机器的一个线程执行
高可用的获取锁与释放锁
高性能的获取锁与释放锁
具备可重入特性(一个线程多次获取同一把锁)
具备锁失效机制,即自动解锁,防止死锁
具备非阻塞特性,即没有获取到锁将直接返回获取锁失败
常用的Redis分布式锁
Redis实现分布式锁的核心便在于SETNX命令,它是SET if Not eXists的缩写,如果键不存在,则将键设置为给定值,在这种情况下,它等于SET;当键已存在时,不执行任何操作;成功时返回1,失败返回0
不过setnx作为锁有一定的缺陷,例如宕机后无法实现对锁的释放,因此会在SET key value [EX seconds] [PX milliseconds] [NX|XX]加入生存时间,不过这种方法也会引起当锁自动释放后,加锁处理逻辑未执行完的问题。
由于引入了看门狗机制
`@Api(tags = "Redis")
@RestController
@RequestMapping("/testRedis")
@Slf4j
public class TestRedisController {
private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder().setNamePrefix("shouhu-").setDaemon(true).build();
private static final ScheduledExecutorService daemonPool = Executors.newScheduledThreadPool(5,THREAD_FACTORY);
@Resource
private RedisTemplate<String ,Object> redisTemplate;
@GetMapping("/testSetNX")
@ApiOperation("SETNX")
public ResultVO<Object> testSetNX(@RequestParam Long goodsId){
String key = "lock_" + goodsId;
String value = UUID.randomUUID().toString();
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
ScheduledFuture<?> scheduledFuture = null;
try {
// 加锁
Boolean ifAbsent = valueOperations.setIfAbsent(key, value, 30, TimeUnit.SECONDS);
log.info("加锁{}返回值:{}",key,ifAbsent);
if ((null==ifAbsent) || (!ifAbsent)){
log.info("加锁失败,请稍后重试!");
return ResultUtils.error("加锁失败,请稍后重试!");
}
// 模拟看门狗逻辑
AtomicInteger count = new AtomicInteger(1);
scheduledFuture = daemonPool.scheduleWithFixedDelay(() -> {
log.info("看门狗第:{}次执行开始", count.get());
Object cache = redisTemplate.opsForValue().get(key);
if (Objects.nonNull(cache) && (value.equals(cache.toString()))) {
// 重新设置有效时间为30秒
redisTemplate.expire(key, 30, TimeUnit.SECONDS);
log.info("看门狗第:{}次执行结束,有效时间为:{}", count.get(), redisTemplate.getExpire(key));
}else {
log.info("看门狗执行第:{}次异常:key:{} 期望值:{} 实际值:{}",count.get(), key, value, cache);
}
count.incrementAndGet();
}, 10, 10, TimeUnit.SECONDS);
// 执行业务逻辑
TimeUnit.SECONDS.sleep(5);
log.info("业务逻辑执行结束");
}catch (Exception e){
log.error("testSetNX exception:",e);
return ResultUtils.sysError();
}finally {
// 释放锁,判断是否是当前线程加的锁
String delVal = valueOperations.get(key).toString();
if (value.equals(delVal)){
Boolean delete = redisTemplate.delete(key);
log.info("释放{}锁结果:{}",key,delete);
// 关闭看门狗线程
if (Objects.nonNull(scheduledFuture)){
boolean cancel = scheduledFuture.cancel(true);
log.info("关闭看门狗结果:{}",cancel);
}
}else {
log.info("不予释放,key:{} value:{} delVal:{}",key,value,delVal);
}
}
return ResultUtils.success("success");
}
}`
2、Redisson是什么
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid),是一款具有诸多高性能功能的综合类开源中间件,提供的功能特性及其在项目中所起的作用远大于原生Redis所提供的各种功能,让开发者对Redis的关注进行分离,可以将更多的精力放在处理业务逻辑上。
什么是红锁?
可以使用红锁来解决主从架构锁失效问题:就是说在主从架构系统中,线程A从master中获取到分布式锁,数据还未同步到slave中时master就挂掉了,slave成为新的master,其它线程从新的master获取锁也成功了,就会出现并发安全问题
红锁算法:
1)应用程序获取系统当前时间,毫秒级
2)应用程序使用相同的key、value值依次从多个Redis实例中获取锁,如果某一个节点超过一定时间仍然没有获取到锁则直接放弃,尽快尝试从下一个Redis节点获取锁,以避免被宕机的节点阻塞。
3)计算获取锁的消耗时间=客户端程序当前时间-step1中的时间,获取锁的消耗时间小于总的锁定时间(例如30s)并且半数以上节点(假如有5个节点,则至少有3个节点)获取锁成功,才认为获取锁成功
4)计算剩余锁定时间=总的锁定时间-step3中的消耗时间
// 用于Redis集群架构下,这些节点是完全独立的,所以不使用复制或任何其他隐式协调系统 // 该对象可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例 @GetMapping("/testRedLock") @ApiOperation("红锁") public ResultVO<Object> testRedLock(@RequestParam Long id) { String threadName = Thread.currentThread().getName(); RLock one = redissonClient.getLock("one_" + id); RLock two = redissonClient.getLock("two_" + id); RLock three = redissonClient.getLock("three_" + id); RedissonMultiLock redLock = new RedissonRedLock(one, two, three); try { redLock.lock(); log.info("{}:获得锁,开始执行业务", threadName); TimeUnit.SECONDS.sleep(2); log.info("{}:执行结束", threadName); return ResultUtils.success(); } catch (Exception e) { log.error("testRedLock exception:", e); return ResultUtils.sysError(); } finally { // 注意:不能使用isLocked()和isHeldByCurrentThread()方法,会抛出UnsupportedOperationException异常 redLock.unlock(); log.info("{}:释放锁成功", threadName); } }
3、使用AOP对Redisson进行封装
其中lock注解为
`@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
/**
* 锁的模式:默认情况下,当参数有多个使用 MULTIPLE 否则使用公平锁
*
* @return the lock model
*/
LockModel lockModel() default LockModel.AUTO;
/** @return [] string [ ] */
String[] keys();
/**
* key的静态常量:当key的spel的值是LIST,数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,<br>
* 而我们如果又需要一个常量的话.这个参数将会在拼接在每个元素的后面
*
* @return String string
*/
String keyConstant() default "";
/**
* 锁超时时间,默认使用配置文件全局设置
*
* @return long long
*/
long lockTime() default 0;
/**
* 等待加锁超时时间,默认使用配置文件全局设置 -1 则表示一直等待
*
* @return long long
*/
long waitTime() default 0;
}核心代码为
@Around(value = "lockAspect(lock)", argNames = "proceedingJoinPoint,lock")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable {
//keys为lock注解的keys
String[] keys = lock.keys();
if (keys.length == 0) {
throw new LockException("the lock keys are required");
}
//获取被注解方法的参数名和参数值
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer()
.getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
Object[] args = proceedingJoinPoint.getArgs();
long waitTime = lock.waitTime();
if (waitTime == 0) {
waitTime = lockProperties.getWaitTime();
}
long lockTime = lock.lockTime();
if (lockTime == 0) {
lockTime = lockProperties.getLockTime();
}
LockModel lockModel = lock.lockModel();
if (lockModel == LockModel.AUTO) {
if (keys.length > 1) {
lockModel = LockModel.RED_LOCK;
} else {
lockModel = LockModel.FAIR;
}
}
if (lockModel != LockModel.MULTIPLE && lockModel != LockModel.RED_LOCK && keys.length > 1) {
throw new RuntimeException("the lock mode " + lockModel.name() + " should have a single parameter");
}
RLock rLock;
List<String> lockKeys = null;
switch (lockModel) {
case FAIR:
lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
//lockKeys.get(0)为锁的名称
rLock = redissonClient.getFairLock(lockKeys.get(0));
break;
case RED_LOCK:
List<RLock> rLocks = new ArrayList<>();
for (String key : keys) {
lockKeys = getValueBySpel(key, parameterNames, args, lock.keyConstant());
rLocks.addAll(lockKeys.stream().map(redissonClient::getLock).collect(Collectors.toList()));
}
rLock = new RedissonRedLock(rLocks.toArray(new RLock[0]));
break;
case MULTIPLE:
rLocks = new ArrayList<>();
for (String key : keys) {
lockKeys = getValueBySpel(key, parameterNames, args, lock.keyConstant());
rLocks.addAll(lockKeys.stream().map(redissonClient::getLock).collect(Collectors.toList()));
}
rLock = new RedissonMultiLock(rLocks.toArray(new RLock[0]));
break;
case REENTRANT:
lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
if (lockKeys.size() == 1) {
rLock = redissonClient.getLock(lockKeys.get(0));
break;
}
rLock = new RedissonRedLock(
lockKeys.stream().map(redissonClient::getLock).toArray(RLock[]::new));
break;
case READ:
lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
rLock = redissonClient.getReadWriteLock(lockKeys.get(0)).readLock();
break;
case WRITE:
lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
rLock = redissonClient.getReadWriteLock(lockKeys.get(0)).writeLock();
break;
default:
throw new LockException("lock model " + lockModel.name() + " is not supported");
}`
通过AOP和Spel解析对加@LOck的函数进行处理
标签:redisson,log,keys,lock,lockKeys,key,Lock,new,分布式 From: https://www.cnblogs.com/dhsfxbk/p/18331063