👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入芋道快速开发平台知识星球。下面是星球提供的部分资料:
👉这是一个或许对你有用的开源项目
国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。
功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:
  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn

1 前言

日常开发中,难免遇到一些并发的场景,为了保证接口执行的一致性,通常采用加锁的方式,因为服务是分布式部署模式,本地锁Reentrantlock和Synchnorized这些就先放到一边了,Redis的setnx锁存在无法抱保证原子性的问题就暂时搁且到一边,直接上大招Redisson也是我最近开发项目中基本都在用的缓存,并且也都是用它的分布式锁机制。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2 Redisson分布式锁常规使用

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

Lock

publicvoidgetLock()
{

//获取锁
    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);

    }

}

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

3 自定义注解实现锁机制

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

自定义注解

@Documented
@Inherited
@Retention
(RetentionPolicy.RUNTIME)

@Target
({ElementType.METHOD})

public@interface
 DistributedLock {

String key()default ""
;


intleaseTime()default 10
;


booleanautoRelease()defaulttrue
;


String errorDesc()default "系统正常处理,请稍后提交"
;


intwaitTime()default 1
;

}

切面类实现

@Aspect
@Component
publicclassDistributedLockHandler
{

privatestaticfinal
 Logger log = LoggerFactory.getLogger(DistributedLockHandler
.class)
;

@Autowired
    RedissonLock redissonLock;


publicDistributedLockHandler()
{

    }


@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) {

thrownew
 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获取锁失败,实现了加锁逻辑。

4 总结

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

欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,长按”或“扫描”下方二维码噢
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
继续阅读
阅读原文