前言

分布式锁想必大家并不陌生:控制分布式系统之间同步访问共享资源的一种方式。

如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

实现分布式锁的方式多种多样,但一般来说都是使用编码的方式在业务代码中穿插加锁解锁逻辑。

这种方式优点是十分模版化,几乎不会出错,每一套加锁解锁逻辑都是一模一样。

缺点也是这个,虽说ctrl c, ctrl v很爽,但一直ctrl c, ctrl v也会让人感到痛苦。

所以,如何基于该思想实现一把注解版的分布式锁呢?

实现目标

业务举例:下单功能为防止用户重复下单,短时间内只允许用户一次点击成功。

@RedisLock(name = "order", keys = {"#userId"})
public String order(Long userId){
  return "ok";
}

如代码所示:只需在方法上加上注解,即可实现分布式锁。

name: 锁的名称

keys: 该业务唯一性资源标识,比如这里是短时间内某个用户只能下一次单,所以keys为用户id

name + keys 组合成redis中的key

设计

大多数由注解设计的功能都是通过切面完成的,这个也不例外。

最简单的实现方式就是将原始代码的加锁解锁逻辑拷贝到切面中实现。

如原始代码如下:

@Resource
private RedissonClient redissonClient;

public String order(Long userId){
  String key = "order" + userId;
  RLock lock = redissonClient.getLock(key);
  try {
    // 尝试加锁, true表示加锁成功
    if (lock.tryLock()) {
      // 业务代码
      return "ok";
    }
    return "fail";
  }finally {
    // 是否为该线程持有锁
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }
}

显然,除了return ok这行代码,其他的都是模板代码。

redissonClient: redisson框架的客户端,官方文档:https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D

AOP切面设计如下:

lock_design

编码

1、定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    /**
     * key的名称前缀,与keys字段共同拼接出一个redis key
     * 如 name = user keys = 123(userId)
     * 最终使用的key为 user123
     */
    String name();

    /**
     * 能够确定出系统中唯一性资源的key
     * 如 用户, 使用用户id为key
     * 或者使用 用户名+手机号
     * 必须为spel表达式 如 #id #user.id #user.name
     */
    String[] keys();
}

2、编写切面

@Aspect
public class RedisLockAspect {

    @Resource
    private RedissonClient redissonClient;

    @Around(value = "@annotation(redisLock)")
    public Object lock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        // 拼接出key 使用spel解析注解中定义的key
        String key = getRedisKey(joinPoint, redisLock);
        // 获取锁
        RLock lock = redissonClient.getLock(key);
        try {
            // 加锁
            if (lock.tryLock()) {
                return joinPoint.proceed();
            }
            throw new RuntimeException("加锁失败");
        }finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

一个超级简单的注解版Redis锁就实现了

读者:“啪!你就写个这破玩意敷衍我呢!”

“别别别,我错了,你听我继续说呀!“

组件库

以上内容只是传播思想,对,思想。

好用的注解版Redis锁我已经实现了。并且已经将它发布到了Maven中央仓库。

关于它的详细文档还请各位移步:https://github.com/lzj960515/kq-universal/tree/main/kq-universal-redis-starter

如果你想知道其中的实现细节,欢迎clone代码阅读或者与我交流

同时,我还想给大伙介绍一下组件库:https://github.com/lzj960515/kq-universal

该组件库为我司内部的后端组件库——部分组件,里面的组件已经久经考验,可以放心使用。

使用这些组件,可以将你的编码效率提升...反正总是会有提升的,我用着是特爽。

如果你想参与进来,欢迎之至,如果你发现里面有bug,感谢大佬!