你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
来源:juejin.cn/post/7215142807861379109
推荐:https://t.zsxq.com/104xTbTKd
自律才能自由
分布式锁,相信大家都不陌生,也都使用过。
最近我在公司的底层框架升级时,发现业务代码中不少地方耦合的使用了 Redisson 分布式锁的原始调用,导致某些有漏洞的框架不好升级。
我联想到了 Spring 中的事务注解,异步注解。我们应该将业务代码,与分布式锁分离。从而实现业务代码与公共分布式锁代码之间的解耦。
于是,我们模仿并实现了一个基于 Redisson 分布式锁的注解,老好用了,分享给大家。
在一些并发的场景,为了保证接口执行的一致性,通常采用加锁的方式,因为服务是分布式部署模式,本地锁 Reentrantlock 和 Synchnorized 这些就先放到一边了。Redis 的 setnx 锁存在无法抱保证原子性的问题就暂时搁且到一边,直接上大招 Redisson 分布式锁。
Redisson分布式锁常规使用
关于 Redisson 的一些基本概念,本文就不做太详细的说明了,有兴趣的小伙伴可以自己去了解下,本文主要内容是注解部分的实现。
下面我们先说下 Redisson 加锁的常规使用。Redisson 分布式锁是基于 Redis 的 Rlock 锁,实现了 JavaJUC 包下的 Lock 接口。
Lock
public void getLock(){
//获取锁
RLock lock = redisson.getLock("Lxlxxx_Lock");
try {
// 2.加锁
lock.lock();
// 设置的自动解锁时间一定要大于业务执行时间,因为在锁时间到了以后,不会自动续期
// lock.lock(10, TimeUnit.SECONDS);
} 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 看门狗机制(https://t.zsxq.com/104xTbTKd),主要作用就是给快过期的锁进行续期,主要用途就是使拿到锁的有限时间让业务执行完,再进行锁释放。
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
@Component
public 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("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 获取锁失败,实现了加锁逻辑。




总结
分布式锁的使用场景还是需要多注意下,根据业务场景来,并发量不大的情况下,根本就没有必要加。
切勿不要盲目加锁,多少会影响一些性能。
本篇文章来源于微信公众号:作者:业余草 业余草 微信号 yyucao 功能介绍 知道的越多,不知道的就越多,业余的像一棵小草一样!我偶尔正经,偶尔抽风!关注我,看我日常如何写 bug,改 bug,背锅吧! 发表于
转载地址:https://mp.weixin.qq.com/s/BgWgKgkxumqbSxtcLZZ4nw
微信扫描下方的二维码阅读本文


Comments NOTHING