写点什么

用注解的方式优雅实现 Ression 分布式锁

  • 2023-03-29
    湖南
  • 本文字数:3028 字

    阅读完需:约 10 分钟

日常开发中,难免遇到一些并发的场景,为了保证接口执行的一致性,通常采用加锁的方式,因为服务是分布式部署模式,本地锁 Reentrantlock 和 Synchnorized 这些就先放到一边了,Redis 的 setnx 锁存在无法抱保证原子性的问题就暂时搁且到一边,直接上大招 Ression 也是我最近开发项目中基本都在用的缓存,并且也都是用它的分布式锁机制。

Ression 分布式锁常规使用

关于 Ression 的一些基本概念,本章就不做太详细的说明了,有兴趣的小伙伴可以自己去了解下,主要说下加锁的常规使用,Ression 分布式锁是基于 Redis 的 Rlock 锁,实现了 JavaJUC 包下的 Lock 接口。

Lock

public void getLock(){    //获取锁    RLock lock = redisson.getLock("Lxlxxx_Lock");    try {        // 2.加锁        lock.lock();
} catch (InterruptedException e) { e.getStackTrace(); } finally { // 3.解锁 lock.unlock(); System.out.println("Finally,释放锁成功"); }
复制代码

getLock 获取锁,lock.lock 进行加锁,会出现的问题就是 lock 拿不到锁一直等待,会进入阻塞状态,显然这样是不好的。

TryLock

返回 boolean 类型,和 Reentrantlock 的 tryLock 是一个意思,尝试获取锁,获取到就返回 true,获取失败就返回 false,不会使获不到锁的线程一直处于等待状态,返回 false 可以继续执行下面的业务逻辑,当然 Ression 锁内部也涉及到 watchDog 看门狗机制,主要作用就是给快过期的锁进行续期,主要用途就是使拿到锁的有限时间让业务执行完,再进行锁释放。

RLock lock = redisson.getLock(name);try {
if (lock.tryLock(2, 10, TimeUnit.SECONDS)) { //执行业务逻辑 } else { System.out.println("已存在"); }} catch (InterruptedException e) { e.printStackTrace();}finally {//判断当前线程持有的锁是不是处于锁定状态,锁定状态再进行释放 if (this.redissonLock.isHeldByCurrentThread(lockName)) { this.redissonLock.unlock(lockName); }}
复制代码

自定义注解实现锁机制

通常我们都会将 redisson 实例注入到方法类里面,然后调用加锁方法进行加锁,如果其他业务方法也需要加锁执行,将会产生很多重复代码,由此采用 AOP 切面的方式,只需要通过注解的方式就能将方法进行加锁处理。

自定义注解

@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface DistributedLock {    String key() default "";
int leaseTime() default 10;
boolean autoRelease() default true;
String errorDesc() default "系统正常处理,请稍后提交";
int waitTime() default 1;}
复制代码

切面类实现

@Aspect@Componentpublic class DistributedLockHandler {    private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class);    @Autowired    RedissonLock redissonLock;
public DistributedLockHandler() { }
@Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { String lockName = this.getRedisKey(joinPoint, distributedLock); int leaseTime = distributedLock.leaseTime(); String errorDesc = distributedLock.errorDesc(); int waitTime = distributedLock.waitTime();
Object var8; try { boolean lock = this.redissonLock.tryLock(lockName, (long)leaseTime, (long)waitTime); if (!lock) { throw new RuntimeException(errorDesc); }
var8 = joinPoint.proceed(); } catch (Throwable var12) { log.error("执行业务方法异常", var12); throw var12; } finally { if (this.redissonLock.isHeldByCurrentThread(lockName)) { this.redissonLock.unlock(lockName); }
}
return var8; }

/** * 获取加锁的key * @param joinPoint * @param distributedLock * @return */ private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) { String key = distributedLock.key(); Object[] parameterValues = joinPoint.getArgs(); MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Method method = signature.getMethod(); DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = nameDiscoverer.getParameterNames(method); if (StringUtils.isEmpty(key)) { if (parameterNames != null && parameterNames.length > 0) { StringBuffer sb = new StringBuffer(); int i = 0;
for(int len = parameterNames.length; i < len; ++i) { sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]); }
key = sb.toString(); } else { key = "redissionLock"; }
return key; } else { SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(key); if (parameterNames != null && parameterNames.length != 0) { EvaluationContext evaluationContext = new StandardEvaluationContext();
for(int i = 0; i < parameterNames.length; ++i) { evaluationContext.setVariable(parameterNames[i], parameterValues[i]); }
try { Object expressionValue = expression.getValue(evaluationContext); return expressionValue != null && !"".equals(expressionValue.toString()) ? expressionValue.toString() : key; } catch (Exception var13) { return key; } } else { return key; } } }}
复制代码

具体使用


方法头加自定义注解,key 参数代表需要加锁的 key,errorDesc 获取锁失败提示报错信息。

这边我将项目通过修改端口启动了两个服务,分别是 8460 和 8461



通过 postman 调用这两个服务,模拟两个服务同时获取一把锁的场景,其中一个服务拿到锁,另外一个服务获取锁失败。


可以看到端口 8460 服务先拿到锁,8461 服务 tryLock 获取锁失败,实现了加锁逻辑。


总结

分布式锁的使用场景还是需要多注意下,根据业务场景来,并发量不大的情况下,其实没有必要加,可能在移动端操作比较频繁的情况下需要注意并发,目前我做的 b 端项目,通过简单接口幂等性操作就可以避免重复提交,切勿不要盲目加锁,多少会影响一些性能。


作者:Lxlxxx

链接:https://juejin.cn/post/7215142807861379109

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
用注解的方式优雅实现Ression分布式锁_Java_做梦都在改BUG_InfoQ写作社区