【分布式】通过Zookeeper与Redisson实现Spring注解式分布式锁


在分布式系统中,由于服务部署在多个节点中,在运行时数据层面上天生无法互通,因此在处理多线程敏感的数据时,无法像单机应用开发一样通过java内部实现的多线程方案来解决。此时我们就需要通过分布式锁的方式来处理数据。

本文将提供一个在spring-cloud基础上通过ZooKeeper或者Redisson实现的注解式的加锁方案。需要使用到Spring AOP,通过ZooKeeper加锁时需要ZooKeeper服务,使用Redisson加锁时需要使用Redis数据库。

依赖

当使用Redisson方式时需要引入Redisson依赖,版本号自行匹配替换。

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>${redisson.version}</version>
</dependency>

当使用ZooKeeper方式时需要引入连接ZooKeeper的依赖,版本号自行匹配替换。

<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>${curator-recipes.version}</version>
</dependency>

配置访问锁服务的组件

Redisson

使用redisson作为锁服务的实现时,需要如下配置类配置RedissonClient组件。

@Configuration
public class RedissonLockConfig {
    @Value("${cloud.lock.redis.url:redis://localhost:6379}")
    private String redisUrl;
    @Value("${cloud.lock.redis.password:}")
    private String redisPassword;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setAddress(redisUrl);
        if (StringUtils.isNotEmpty(redisPassword)) {
            singleServerConfig.setPassword(redisPassword);
        }
        return Redisson.create(config);
    }
}

其中redisUrl为Redis服务的地址,redisPassword为Redis服务的密码。

RedissonClient连接配置可以按需进行调整。

ZooKeeper

使用ZooKeeper作为锁服务的实现时,需要如下配置类配置CuratorFramework组件。

@Configuration
public class ZooKeeperLockConfig {
    @Value("${cloud.lock.zookeeper.url:localhost:2181}")
    private String url;
    @Value("${cloud.lock.zookeeper.timeout:1000}")
    private int timeout;
    @Value("${cloud.lock.zookeeper.retry:3}")
    private int retry;

    @Bean
    public CuratorFramework zkClient() {
        ExponentialBackoffRetry exponentialBackoffRetry = new ExponentialBackoffRetry(timeout, retry);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(url, exponentialBackoffRetry);
        curatorFramework.start();
        return curatorFramework;
    }
}

其中url是ZooKeeper服务地址,timeout是连接超时时间,retry是连接失败重试次数。

CuratorFramework连接配置可以按需进行调整。

加锁逻辑

在分布式系统中,多系统之间的数据很可能不互通,因此加锁肯定不是通过原始数据进行加锁。

在加锁时,我们应该对应原始数据,生成唯一的对应该数据的token作为加锁依据的key,同时该key在无论何时对于该原始数据而言生成的都是这个key。这样一来,通过该key明确地指向原始数据进行加锁与解锁。

例如在系统中,存在张User表,该表中id列为其主键。那么在进行数据加锁时,则可以考虑以如下格式:“User-[id数据]”来进行加锁解锁操作。

加锁实现

在进行实现时,首先考虑到一次加锁时可能涉及到多个数据,因此使用key集合来进行加锁,timeout与timeunit用于指代锁的超时时长。

抽象出如下方法:

public abstract LockEntity doLock(List<String> keys, Long timeout, TimeUnit unit) throws Exception;

LockEntity为锁实例,用于进行解锁操作

public class LockEntity {
    private final Object lock;

    LockEntity(Object lock) {
        this.lock = lock;
    }

    public void unlock() throws Exception {
        if (lock instanceof RLock rLock) {
            rLock.unlock();
        } else if (lock instanceof InterProcessLock interProcessLock) {
            interProcessLock.release();
        }
    }
}

针对不同的锁类型,采取不同的解锁方法

Redisson

逻辑:先通过RedissonClient组件遍历keys集合获取所有锁实例,再将以这些锁实例来获取一个MultiLock实例,来进行多锁的统一加锁解锁,再根据提供的timeout和unit加锁并返回LockEntity对象用来操作解锁。

源码:

public LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) {
    ArrayList<RLock> rLocks = new ArrayList<>(keys.size());
    for (String key : keys) {
        rLocks.add(redissonClient.getLock(key));
    }
    RLock multiLock = redissonClient.getMultiLock(rLocks.toArray(new RLock[0]));
    if (timeout != null && timeout > 0 && unit != null) {
        multiLock.lock(timeout, unit);
    } else {
        multiLock.lock();
    }
    return new LockEntity(multiLock);
}

ZooKeeper

逻辑:同Redisson类似,先构造多锁对象,然后使用该对象进行加锁并返回

源码:

public LockEntity doLock(Set<String> lockKeys, Long timeout, TimeUnit unit) throws Exception {
    InterProcessMultiLock multiLock = new InterProcessMultiLock(zkClient, lockKeys.stream().toList());
    if (timeout != null && timeout > 0 && unit != null) {
        multiLock.acquire(timeout, unit);
    } else {
        multiLock.acquire();
    }
    return new LockEntity(multiLock);
}

实现通过注解加锁

定义注解
  1. 首先针对锁的实现方式,定义区分注解:

    • @RedisLock

      @Target({ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface RedisLock {
          long timeout() default 0;
      
          TimeUnit unit() default TimeUnit.SECONDS;
      }
      
    • @ZkLock

      @Target({ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ZkLock {
          long timeout() default 0;
      
          TimeUnit unit() default TimeUnit.SECONDS;
      }
      

这两个注解作用于方法上,用于表明当前方法需要使用到分布式锁,以及需要使用到哪种分布式锁,并提供锁的超时时间。

  1. 其次针对方法参数中的的同步参数,提供一个注解@SyncArg。
 @Target({ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface SyncArg {
     /**
      * @return 加锁依据,填入Spel表达式,用于生成锁key
      * 应该确保每一个加锁对象能够生成一个固定的且独立的key
      * 使用时可用上下文包括 arg:当前参数,method:当前运行的方法
      */
     String value() default "#arg.hashCode()";
 
     /**
      * @return 是否采用多对象解析方式,多对象时将产生多个锁并同步加锁
      * 当传入对象实现了 Iterable时,将遍历每一个子项,并且生成key时arg为当前遍历到的对象
      * 当传入对象实现了Map时,将遍历每个Entry,生成key时arg为当前Entry
      */
     boolean isMulti() default false;
 }

该注解作用于方法参数,表明该参数需要参照value值生成key来加锁,isMulti用于标识当前参数是否是多对象参数。

处理注解

首先,通过一个上下文对象用来自动生成加锁的key。

@Getter
@Setter
public class LockContext {
    /**
     * 被加锁的参数对象
     */
    private Object arg;
    /**
     * 生成key的Spel表达式
     */
    private String keyExpression;
    /**
     * 是否为多对象
     */
    private boolean multi;
    /**
     * 上下文参数
     */
    @Getter(AccessLevel.PACKAGE)
    private final Map<String, Object> params;

    public LockContext(SyncArg syncArg, Object arg) {
        this(arg, syncArg.value(), syncArg.isMulti());
    }

    public LockContext(Object arg, String keyExpression, boolean multi) {
        this(arg, keyExpression, multi, new HashMap<>());
    }

    public LockContext(Object arg, String keyExpression, boolean multi, Map<String, Object> params) {
        this.arg = arg;
        this.keyExpression = keyExpression;
        this.multi = multi;
        this.params = params;
    }

    /**
     * 添加上下文参数
     *
     * @param key   key
     * @param value value
     * @return 用于链式调用
     */
    public LockContext addParam(String key, Object value) {
        params.put(key, value);
        return this;
    }

    /**
     * 删除上下文参数
     *
     * @param key key
     * @return 用于链式调用
     */
    public LockContext removeParam(String key) {
        params.remove(key);
        return this;
    }

    @Override
    public String toString() {
        return getKey(new SpelExpressionParser());
    }

    /**
     * 生成key
     *
     * @param parser Spel表达式解析器
     * @return key
     */
    public String getKey(SpelExpressionParser parser) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        HashMap<String, Object> contextMap = new HashMap<>(params);
        if (!contextMap.containsKey("arg")) {
            contextMap.put("arg", arg);
        }
        context.setVariables(contextMap);
        return parser.parseExpression(keyExpression).getValue(context, String.class);
    }
}

这里通过继承抽象类的方式在抽象类中提供一些公共方法,由子类进行加锁的实现。

public abstract class AbstractLockService {
    /**
     * 格式化生成的key,在生成加锁key时会经过该方法,并以返回的key作为最终加锁时的key
     *
     * @param generatedKey 通过上下文对象自动生成的key
     * @return 格式化后的key
     */
    public abstract String formatKey(String generatedKey);

    /**
     * 通过LockContext的形式生成锁key再加锁
     *
     * @param lockContexts 用于生成锁key
     * @param timeout      锁超时时间
     * @param unit         时间单位
     * @return 锁对象
     */
    protected LockEntity doLockByContext(List<LockContext> lockContexts, Long timeout, TimeUnit unit) throws Exception {
        return doLock(getLockKeys(lockContexts), timeout, unit);
    }

    public LockEntity doLock(Set<String> keys) throws Exception {
        return doLock(keys, null, null);
    }

    /**
     * 通过锁keys进行加锁,由子类实现
     *
     * @param keys    锁key
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return 锁对象
     */
    public abstract LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) throws Exception;

    /**
     * 根据方法及入参解析出LockContext列表
     *
     * @param method 加锁的方法
     * @param args   入参
     * @return LockContext列表
     */
    protected List<LockContext> getLockContexts(Method method, Object[] args) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        ArrayList<LockContext> lockContexts = new ArrayList<>();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            //获取参数注解,与参数一一对应
            Annotation[] annotations = parameterAnnotations[i];
            //找到@SyncArg注解
            SyncArg syncArg = null;
            for (Annotation annotation : annotations) {
                if (annotation instanceof SyncArg syncArgA) {
                    syncArg = syncArgA;
                    break;
                }
            }
            if (syncArg != null) {
                //注解存在则通过注解构造上下文对象
                LockContext lockContext = new LockContext(syncArg, args[i]);
                lockContext.addParam("method", method);
                lockContexts.add(lockContext);
            }
        }
        return lockContexts;
    }

    /**
     * 通过上下文对象列表生成key集合
     *
     * @param lockContexts 上下文对象列表
     * @return key集合
     */
    protected HashSet<String> getLockKeys(List<LockContext> lockContexts) {
        //解析上下文列表,生成key
        HashSet<String> keys = new HashSet<>();
        SpelExpressionParser parser = new SpelExpressionParser();
        for (LockContext lockContext : lockContexts) {
            boolean multi = lockContext.isMulti();
            Object arg = lockContext.getArg();
            String keyExpression = lockContext.getKeyExpression();
            if (multi && arg instanceof Iterable<?> iterable) {
                //多对象模式且参数对象为Iterable时
                for (Object o : iterable) {
                    LockContext singleContext = new LockContext(o, keyExpression, false, lockContext.getParams());
                    keys.add(formatKey(singleContext.getKey(parser)));
                }
            } else if (multi && arg instanceof Map<?, ?> map) {
                //多对象模式且参数对象为map时
                for (Map.Entry<?, ?> entry : map.entrySet()) {
                    LockContext singleContext = new LockContext(entry, keyExpression, false, lockContext.getParams());
                    keys.add(formatKey(singleContext.getKey(parser)));
                }
            } else {
                keys.add(formatKey(lockContext.getKey(parser)));
            }
        }
        return keys;
    }
}
Redisson实现
@Slf4j
@Aspect
@Component
public class RedissonLockService extends AbstractLockService {
    private final RedissonClient redissonClient;

    @Autowired
    public RedissonLockService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.RedisLock)")
    private void doLockByContext() {
    }

    @Around("doLockByContext()")
    private Object around(ProceedingJoinPoint point) throws Throwable {
        //获取方法对象
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //解析用于生成锁的上下文
        List<LockContext> lockContexts = getLockContexts(method, point.getArgs());
        //加锁并执行
        RedisLock lock = method.getAnnotation(RedisLock.class);
        LockEntity lockEntity = doLockByContext(lockContexts, lock.timeout(), lock.unit());
        try {
            return point.proceed();
        } finally {
            lockEntity.unlock();
        }
    }

    @Override
    public String formatKey(String generatedKey) {
        return "cloud:lock:" + generatedKey;
    }

    @Override
    public LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) {
        ArrayList<RLock> rLocks = new ArrayList<>(keys.size());
        for (String key : keys) {
            rLocks.add(redissonClient.getLock(key));
        }
        RLock multiLock = redissonClient.getMultiLock(rLocks.toArray(new RLock[0]));
        if (timeout != null && timeout > 0 && unit != null) {
            multiLock.lock(timeout, unit);
        } else {
            multiLock.lock();
        }
        return new LockEntity(multiLock);
    }
}

通过AOP的方法,在@RedisLock注解处创建切面,在执行该方法之前先对该方法的参数进行处理,产生上下文列表,并依据该列表生成对应的key集合,再通过该key集合尝试获取锁。在获取锁成功后再执行对应的方法,在finally代码块中释放锁。

ZooKeeper实现
@Slf4j
@Aspect
@Component
public class ZooKeeperLockService extends AbstractLockService {
    private final CuratorFramework zkClient;

    @Autowired
    public ZooKeeperLockService(CuratorFramework zkClient) {
        this.zkClient = zkClient;
    }

    @Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.ZkLock)")
    private void doLockByContext() {

    }

    @Around("doLockByContext()")
    private Object around(ProceedingJoinPoint point) throws Throwable {
        //获取方法对象
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //解析用于生成锁的上下文
        List<LockContext> lockContexts = getLockContexts(method, point.getArgs());
        //加锁并执行
        ZkLock zkLock = method.getAnnotation(ZkLock.class);
        LockEntity lockEntity = doLockByContext(lockContexts, zkLock.timeout(), zkLock.unit());
        try {
            return point.proceed();
        } finally {
            lockEntity.unlock();
        }
    }

    @Override
    public String formatKey(String generatedKey) {
        return "/cloud/lock/" + generatedKey;
    }

    @Override
    public LockEntity doLock(Set<String> lockKeys, Long timeout, TimeUnit unit) throws Exception {
        InterProcessMultiLock multiLock = new InterProcessMultiLock(zkClient, lockKeys.stream().toList());
        if (timeout != null && timeout > 0 && unit != null) {
            multiLock.acquire(timeout, unit);
        } else {
            multiLock.acquire();
        }
        return new LockEntity(multiLock);
    }
}

同Redisson类似,在ZkLock处创建切面,先根据注解解析对应参数的锁key后,再获取锁,然后执行方法并释放锁。

完整代码

以下按照包结构排列

    • lock

      • annotation

        • RedisLock

          @Target({ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface RedisLock {
              long timeout() default 0;
          
              TimeUnit unit() default TimeUnit.SECONDS;
          }
          
        • SyncArg

          @Target({ElementType.PARAMETER})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface SyncArg {
              /**
               * @return 加锁依据,填入Spel表达式,用于生成锁key
               * 应该确保每一个加锁对象能够生成一个固定的且独立的key
               * 使用时可用上下文包括 arg:当前参数,method:当前运行的方法
               */
              String value() default "#arg.hashCode()";
          
              /**
               * @return 是否采用多对象解析方式,多对象时将产生多个锁并同步加锁
               * 当传入对象实现了 Iterable时,将遍历每一个子项,并且生成key时arg为当前遍历到的对象
               * 当传入对象实现了Map时,将遍历每个Entry,生成key时arg为当前Entry
               */
              boolean isMulti() default false;
          }
          
        • ZkLock

          @Target({ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface ZkLock {
              long timeout() default 0;
          
              TimeUnit unit() default TimeUnit.SECONDS;
          }
          
      • config

        • RedissonLockConfig

          @Configuration
          public class RedissonLockConfig {
              @Value("${cloud.lock.redis.url:redis://localhost:6379}")
              private String redisUrl;
              @Value("${cloud.lock.redis.password:}")
              private String redisPassword;
          
              @Bean
              public RedissonClient redissonClient() {
                  Config config = new Config();
                  SingleServerConfig singleServerConfig = config.useSingleServer();
                  singleServerConfig.setAddress(redisUrl);
                  if (StringUtils.isNotEmpty(redisPassword)) {
                      singleServerConfig.setPassword(redisPassword);
                  }
                  return Redisson.create(config);
              }
          }
          
        • ZooKeeperLockConfig

          @Configuration
          public class ZooKeeperLockConfig {
              @Value("${cloud.lock.zookeeper.url:localhost:2181}")
              private String url;
              @Value("${cloud.lock.zookeeper.timeout:1000}")
              private int timeout;
              @Value("${cloud.lock.zookeeper.retry:3}")
              private int retry;
          
              @Bean
              public CuratorFramework zkClient() {
                  ExponentialBackoffRetry exponentialBackoffRetry = new ExponentialBackoffRetry(timeout, retry);
                  CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(url, exponentialBackoffRetry);
                  curatorFramework.start();
                  return curatorFramework;
              }
          }
          
      • AbstractLockService

        public abstract class AbstractLockService {
            /**
             * 格式化生成的key,在生成加锁key时会经过该方法,并以返回的key作为最终加锁时的key
             *
             * @param generatedKey 通过上下文对象自动生成的key
             * @return 格式化后的key
             */
            public abstract String formatKey(String generatedKey);
        
            /**
             * 通过LockContext的形式生成锁key再加锁
             *
             * @param lockContexts 用于生成锁key
             * @param timeout      锁超时时间
             * @param unit         时间单位
             * @return 锁对象
             */
            protected LockEntity doLockByContext(List<LockContext> lockContexts, Long timeout, TimeUnit unit) throws Exception {
                return doLock(getLockKeys(lockContexts), timeout, unit);
            }
        
            public LockEntity doLock(Set<String> keys) throws Exception {
                return doLock(keys, null, null);
            }
        
            /**
             * 通过锁keys进行加锁,由子类实现
             *
             * @param keys    锁key
             * @param timeout 超时时间
             * @param unit    时间单位
             * @return 锁对象
             */
            public abstract LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) throws Exception;
        
            /**
             * 根据方法及入参解析出LockContext列表
             *
             * @param method 加锁的方法
             * @param args   入参
             * @return LockContext列表
             */
            protected List<LockContext> getLockContexts(Method method, Object[] args) {
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                ArrayList<LockContext> lockContexts = new ArrayList<>();
                for (int i = 0; i < parameterAnnotations.length; i++) {
                    //获取参数注解,与参数一一对应
                    Annotation[] annotations = parameterAnnotations[i];
                    //找到@SyncArg注解
                    SyncArg syncArg = null;
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof SyncArg syncArgA) {
                            syncArg = syncArgA;
                            break;
                        }
                    }
                    if (syncArg != null) {
                        //注解存在则通过注解构造上下文对象
                        LockContext lockContext = new LockContext(syncArg, args[i]);
                        lockContext.addParam("method", method);
                        lockContexts.add(lockContext);
                    }
                }
                return lockContexts;
            }
        
            /**
             * 通过上下文对象列表生成key集合
             *
             * @param lockContexts 上下文对象列表
             * @return key集合
             */
            protected HashSet<String> getLockKeys(List<LockContext> lockContexts) {
                //解析上下文列表,生成key
                HashSet<String> keys = new HashSet<>();
                SpelExpressionParser parser = new SpelExpressionParser();
                for (LockContext lockContext : lockContexts) {
                    boolean multi = lockContext.isMulti();
                    Object arg = lockContext.getArg();
                    String keyExpression = lockContext.getKeyExpression();
                    if (multi && arg instanceof Iterable<?> iterable) {
                        //多对象模式且参数对象为Iterable时
                        for (Object o : iterable) {
                            LockContext singleContext = new LockContext(o, keyExpression, false, lockContext.getParams());
                            keys.add(formatKey(singleContext.getKey(parser)));
                        }
                    } else if (multi && arg instanceof Map<?, ?> map) {
                        //多对象模式且参数对象为map时
                        for (Map.Entry<?, ?> entry : map.entrySet()) {
                            LockContext singleContext = new LockContext(entry, keyExpression, false, lockContext.getParams());
                            keys.add(formatKey(singleContext.getKey(parser)));
                        }
                    } else {
                        keys.add(formatKey(lockContext.getKey(parser)));
                    }
                }
                return keys;
            }
        }
        
      • LockContext

        @Getter
        @Setter
        public class LockContext {
            /**
             * 被加锁的参数对象
             */
            private Object arg;
            /**
             * 生成key的Spel表达式
             */
            private String keyExpression;
            /**
             * 是否为多对象
             */
            private boolean multi;
            /**
             * 上下文参数
             */
            @Getter(AccessLevel.PACKAGE)
            private final Map<String, Object> params;
        
            public LockContext(SyncArg syncArg, Object arg) {
                this(arg, syncArg.value(), syncArg.isMulti());
            }
        
            public LockContext(Object arg, String keyExpression, boolean multi) {
                this(arg, keyExpression, multi, new HashMap<>());
            }
        
            public LockContext(Object arg, String keyExpression, boolean multi, Map<String, Object> params) {
                this.arg = arg;
                this.keyExpression = keyExpression;
                this.multi = multi;
                this.params = params;
            }
        
            /**
             * 添加上下文参数
             *
             * @param key   key
             * @param value value
             * @return 用于链式调用
             */
            public LockContext addParam(String key, Object value) {
                params.put(key, value);
                return this;
            }
        
            /**
             * 删除上下文参数
             *
             * @param key key
             * @return 用于链式调用
             */
            public LockContext removeParam(String key) {
                params.remove(key);
                return this;
            }
        
            @Override
            public String toString() {
                return getKey(new SpelExpressionParser());
            }
        
            /**
             * 生成key
             *
             * @param parser Spel表达式解析器
             * @return key
             */
            public String getKey(SpelExpressionParser parser) {
                StandardEvaluationContext context = new StandardEvaluationContext();
                HashMap<String, Object> contextMap = new HashMap<>(params);
                if (!contextMap.containsKey("arg")) {
                    contextMap.put("arg", arg);
                }
                context.setVariables(contextMap);
                return parser.parseExpression(keyExpression).getValue(String.class);
            }
        }
        
      • LockEntity

        public class LockEntity {
            private final Object lock;
        
            LockEntity(Object lock) {
                this.lock = lock;
            }
        
            public void unlock() throws Exception {
                if (lock instanceof RLock rLock) {
                    rLock.unlock();
                } else if (lock instanceof InterProcessLock interProcessLock) {
                    interProcessLock.release();
                }
            }
        }
        
      • RedissonLockService

        @Slf4j
        @Aspect
        @Component
        public class RedissonLockService extends AbstractLockService {
            private final RedissonClient redissonClient;
        
            @Autowired
            public RedissonLockService(RedissonClient redissonClient) {
                this.redissonClient = redissonClient;
            }
        
            @Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.RedisLock)")
            private void doLockByContext() {
            }
        
            @Around("doLockByContext()")
            private Object around(ProceedingJoinPoint point) throws Throwable {
                //获取方法对象
                MethodSignature signature = (MethodSignature) point.getSignature();
                Method method = signature.getMethod();
                //解析用于生成锁的上下文
                List<LockContext> lockContexts = getLockContexts(method, point.getArgs());
                //加锁并执行
                RedisLock lock = method.getAnnotation(RedisLock.class);
                LockEntity lockEntity = doLockByContext(lockContexts, lock.timeout(), lock.unit());
                try {
                    return point.proceed();
                } finally {
                    lockEntity.unlock();
                }
            }
        
            @Override
            public String formatKey(String generatedKey) {
                return "cloud:lock:" + generatedKey;
            }
        
            @Override
            public LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) {
                ArrayList<RLock> rLocks = new ArrayList<>(keys.size());
                for (String key : keys) {
                    rLocks.add(redissonClient.getLock(key));
                }
                RLock multiLock = redissonClient.getMultiLock(rLocks.toArray(new RLock[0]));
                if (timeout != null && timeout > 0 && unit != null) {
                    multiLock.lock(timeout, unit);
                } else {
                    multiLock.lock();
                }
                return new LockEntity(multiLock);
            }
        }
        
      • ZooKeeper

        @Slf4j
        @Aspect
        @Component
        public class ZooKeeperLockService extends AbstractLockService {
            private final CuratorFramework zkClient;
        
            @Autowired
            public ZooKeeperLockService(CuratorFramework zkClient) {
                this.zkClient = zkClient;
            }
        
            @Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.ZkLock)")
            private void doLockByContext() {
        
            }
        
            @Around("doLockByContext()")
            private Object around(ProceedingJoinPoint point) throws Throwable {
                //获取方法对象
                MethodSignature signature = (MethodSignature) point.getSignature();
                Method method = signature.getMethod();
                //解析用于生成锁的上下文
                List<LockContext> lockContexts = getLockContexts(method, point.getArgs());
                //加锁并执行
                ZkLock zkLock = method.getAnnotation(ZkLock.class);
                LockEntity lockEntity = doLockByContext(lockContexts, zkLock.timeout(), zkLock.unit());
                try {
                    return point.proceed();
                } finally {
                    lockEntity.unlock();
                }
            }
        
            @Override
            public String formatKey(String generatedKey) {
                return "/cloud/lock/" + generatedKey;
            }
        
            @Override
            public LockEntity doLock(Set<String> lockKeys, Long timeout, TimeUnit unit) throws Exception {
                InterProcessMultiLock multiLock = new InterProcessMultiLock(zkClient, lockKeys.stream().toList());
                if (timeout != null && timeout > 0 && unit != null) {
                    multiLock.acquire(timeout, unit);
                } else {
                    multiLock.acquire();
                }
                return new LockEntity(multiLock);
            }
        }
        

测试效果

采用如下Spring服务类进行效果测试,其中run中为测试代码,表达式“#arg.getClass().getSimpleName()+#arg.getId()”以id作为唯一主键生成key,当前示例下生成的key为Usertest01。

@Slf4j
@Service
public class TestService {

    @Getter
    @Setter
    public static class User {
        private String id = "test01";
        private String name = "测试用户";
    }

    public void test(User user) throws InterruptedException {
        log.info(user.getName());
        Thread.sleep(1000);//模拟业务过程
    }

    @RedisLock
    public void testRedisSync(
            @SyncArg("#arg.getClass().getSimpleName()+#arg.getId()") User user
    ) throws InterruptedException {
        test(user);
    }

    @ZkLock
    public void testZooKeeperSync(
            @SyncArg("#arg.getClass().getSimpleName()+#arg.getId()") User user
    ) throws InterruptedException {
        test(user);
    }
}

不加锁

@Service
public class TestRunner implements CommandLineRunner {
    @Resource
    private TestService testService;

    @Override
    public void run(String... args) throws Exception {
        TestService.User user = new TestService.User();
        for (int i = 0; i < 1000; i++) {
            testService.test(user);
        }
    }
}

服务一:

不加锁服务1log

服务二:

不加锁服务2log

根据时间可以看到两个服务几乎同时在进行,如果存在变更数据的情况则可能存在出现数据不一致情况的风险。

使用Redis锁

@Service
public class TestRunner implements CommandLineRunner {
    @Resource
    private TestService testService;

    @Override
    public void run(String... args) throws Exception {
        TestService.User user = new TestService.User();
        for (int i = 0; i < 1000; i++) {
            testService.testRedisSync(user);
        }
    }
}

服务一:

Redis锁服务1log

服务二:

Redis锁服务2log

根据时间可以看到两个服务在逐一有序地进行,能够实现锁的效果。

使用ZooKeeper锁

@Service
public class TestRunner implements CommandLineRunner {
    @Resource
    private TestService testService;

    @Override
    public void run(String... args) throws Exception {
        TestService.User user = new TestService.User();
        for (int i = 0; i < 1000; i++) {
            testService.testZooKeeperSync(user);
        }
    }
}

服务一:

ZooKeeper服务1log

服务二:

ZooKeeper服务2log

同样能够实现锁的效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值