• 注解方式优雅的实现 Redisson 分布式锁


    1前言

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

    2Redisson分布式锁常规使用

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

    Lock

    1. public void getLock(){
    2. //获取锁
    3. RLock lock = redisson.getLock("Lxlxxx_Lock");
    4. try {
    5. // 2.加锁
    6. lock.lock();
    7. } catch (InterruptedException e) {
    8. e.getStackTrace();
    9. } finally {
    10. // 3.解锁
    11. lock.unlock();
    12. System.out.println("Finally,释放锁成功");
    13. }

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

    TryLock

    返回boolean类型,和Reentrantlock的tryLock是一个意思,尝试获取锁,获取到就返回true

    1. RLock lock = redisson.getLock(name);
    2. try {
    3. if (lock.tryLock(2, 10, TimeUnit.SECONDS)) {
    4. //执行业务逻辑
    5. } else {
    6. System.out.println("已存在");
    7. }
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }finally {
    11. //判断当前线程持有的锁是不是处于锁定状态,锁定状态再进行释放
    12. if (this.redissonLock.isHeldByCurrentThread(lockName)) {
    13. this.redissonLock.unlock(lockName);
    14. }
    15. }

    3自定义注解实现锁机制

    通常我们都会将redisson实例注入到方法类里面,然后调用加锁方法进行加锁,如果其他业务方法也需要加锁执行,将会产生很多重复代码,由此采用AOP切面的方式,只需要通过注解的方式就能将方法进行加锁处理。另外,搜索公众号Linux就该这样学后台回复“猴子”,获取一份惊喜礼包。

    自定义注解

    1. @Documented
    2. @Inherited
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Target({ElementType.METHOD})
    5. public @interface DistributedLock {
    6. String key() default "";
    7. int leaseTime() default 10;
    8. boolean autoRelease() default true;
    9. String errorDesc() default "系统正常处理,请稍后提交";
    10. int waitTime() default 1;
    11. }

    切面类实现

    1. @Aspect
    2. @Component
    3. public class DistributedLockHandler {
    4. private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class);
    5. @Autowired
    6. RedissonLock redissonLock;
    7. public DistributedLockHandler() {
    8. }
    9. @Around("@annotation(distributedLock)")
    10. public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
    11. String lockName = this.getRedisKey(joinPoint, distributedLock);
    12. int leaseTime = distributedLock.leaseTime();
    13. String errorDesc = distributedLock.errorDesc();
    14. int waitTime = distributedLock.waitTime();
    15. Object var8;
    16. try {
    17. boolean lock = this.redissonLock.tryLock(lockName, (long)leaseTime, (long)waitTime);
    18. if (!lock) {
    19. throw new RuntimeException(errorDesc);
    20. }
    21. var8 = joinPoint.proceed();
    22. } catch (Throwable var12) {
    23. log.error("执行业务方法异常", var12);
    24. throw var12;
    25. } finally {
    26. if (this.redissonLock.isHeldByCurrentThread(lockName)) {
    27. this.redissonLock.unlock(lockName);
    28. }
    29. }
    30. return var8;
    31. }
    32. /**
    33. * 获取加锁的key
    34. * @param joinPoint
    35. * @param distributedLock
    36. * @return
    37. */
    38. private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
    39. String key = distributedLock.key();
    40. Object[] parameterValues = joinPoint.getArgs();
    41. MethodSignature signature = (MethodSignature)joinPoint.getSignature();
    42. Method method = signature.getMethod();
    43. DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    44. String[] parameterNames = nameDiscoverer.getParameterNames(method);
    45. if (StringUtils.isEmpty(key)) {
    46. if (parameterNames != null && parameterNames.length > 0) {
    47. StringBuffer sb = new StringBuffer();
    48. int i = 0;
    49. for(int len = parameterNames.length; i < len; ++i) {
    50. sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]);
    51. }
    52. key = sb.toString();
    53. } else {
    54. key = "redissionLock";
    55. }
    56. return key;
    57. } else {
    58. SpelExpressionParser parser = new SpelExpressionParser();
    59. Expression expression = parser.parseExpression(key);
    60. if (parameterNames != null && parameterNames.length != 0) {
    61. EvaluationContext evaluationContext = new StandardEvaluationContext();
    62. for(int i = 0; i < parameterNames.length; ++i) {
    63. evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
    64. }
    65. try {
    66. Object expressionValue = expression.getValue(evaluationContext);
    67. return expressionValue != null && !"".equals(expressionValue.toString()) ? expressionValue.toString() : key;
    68. } catch (Exception var13) {
    69. return key;
    70. }
    71. } else {
    72. return key;
    73. }
    74. }
    75. }
    76. }

    具体使用

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

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

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

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

    分布式锁的使用场景还是需要多注意下,根据业务场景来,并发量不大的情况下,其实没有

  • 相关阅读:
    如何避免在编码层面产生质量事故
    【项目经验】:elementui多选表格默认选中
    在 Python 中创建 Getter 和 Setter
    间歇性禁食 & 肠道菌群 & 心血管代谢疾病
    【字符编码系列一】ASCII编码是什么?
    QtConcurrent使用成员函数:QT5&QT6(老写法报错)的区别
    化工机械基础试题及答案
    【分享】我们要接入的应用并不在集简云的列表中怎么办?
    [英雄星球六月集训LeetCode解题日报] 第29日 分治
    【App自动化测试】(八)三种等待方式——强制等待、隐式等待、显示等待
  • 原文地址:https://blog.csdn.net/caryxp/article/details/134524932