shared-lock
介绍
分布式共享锁,支持多种模式
- try/finally 原始模式
 - callback 回调模式,类似 JdbcTemplate execute
 - AOP 切面注解模式
 
支持
- 重入
 - 降级
 
支持 enable 引用 和 starter 的开箱即用方式。
一、快速开始
1、MAVEN 引用
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>spring-boot-starter-shared-lock</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- redis -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-redis</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- zookeeper -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-zookeeper</artifactId>
   <version>${lastVersion}</version>
</dependency> 
 2. try/finally 使用
    // 来一个 try-with-resource 模式
    try(ISharedLock lock = SharedLockBuilder.builder(LOCK_KEY).build()) {
      if (lock.tryLock(5, TimeUnit.SECONDS)) {
        System.out.println("执行成功");
        return Flux.just("OK");
      } else {
        return Flux.error(new Throwable("失败了"));
      }
    }
 
 3. AOP 方式使用
package org.shared.lock.demo;
import lombok.extern.slf4j.Slf4j;
import net.madtiger.lock.SharedLock;
import org.springframework.stereotype.Service;
/**
 * 样例
 * @author Fenghu.Shi
 * @version 1.0
 */
@Service
@Slf4j
public class DemoService {
  /**
   * 执行入口
   * @param abc
   * @return
   */
  @SharedLock(key ="demo-test-#{abc}", fallbackMethod = "faultBack", rollbackMethod = "rollback")
  public String testAopLock(String abc){
      log.error("我获取到锁了");
      return "这是我得知";
  }
  /**
   * 失败降级方法
   * @param bac
   * @return
   */
  public String faultBack(String bac){
    log.error("我被降级了");
    return "降级哈哈";
  }
  /**
   * 回滚方法
   * @param abc
   */
  public String rollback(String abc){
    System.out.println("回滚了");
    return "我被回滚了";
  }
}
 
 二、软件架构
需要 java 8+ ,同时依赖 spring
-  
redis 依赖 spring data reids
 -  
zookeeper 依赖 Curator Framework
 
名词说明
| 英文名称 | 中文名称 | 描述 | 
|---|---|---|
| ISharedLock | 锁持有对象 | 锁的持有者和操作者 | 
| Provider | 服务提供者 | 最终底层实现的底层服务,比如 ZooKeeper,Redis,都会有对应的Provider | 
| Builder | 构造模式 | 用于快速链式构造对象,如 SharedLockBuilder | 
| Environment | 环境 | 用于定义SharedLock环境信息,如 SharedLockEnvironment | 
| Decorator | 装饰者 | SharedLock 内部除了基本功能意外的特性都是通过 Decorator 实现的,如 自旋锁(redis 支持),可重入锁 | 
三、安装教程
安装方式,主要支持两三种自动注入方式:enabled 、spring boot starter 和 自定义
enabled 模式
- maven 导入
 
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-annotation</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- redis -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-redis</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- zookeeper -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-zookeeper</artifactId>
   <version>${lastVersion}</version>
</dependency>
 
 - 开启引用
 
@SpringBootApplication
@EnabledSharedLock // 开启自动注入
public class DisLockApplication {
    public static void main(String[] args) {
        SpringApplication.run(DisLockApplication.class, args);
    }
} 
 - 配置 spring data redis / curator framework
 
这里小伙伴们自己配置吧
spring boot starter 模式
- maven 导入
 
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>spring-boot-starter-shared-lock</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- redis -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-redis</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- zookeeper -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-zookeeper</artifactId>
   <version>${lastVersion}</version>
</dependency> 
 - 配置 spring data redis
 
这里小伙伴们自己配置吧
~~~自定义 (该方式不推荐使用)~~~
此方式稍微麻烦点,不建议大家使用哈
1. maven 导入
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-annotation</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- redis -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-redis</artifactId>
   <version>${lastVersion}</version>
</dependency>
<!-- zookeeper -->
<dependency>
   <groupId>net.madtiger.shared.lock</groupId>
   <artifactId>lock-zookeeper</artifactId>
   <version>${lastVersion}</version>
</dependency> 
 2. 初始化 service bean
SharedLock 组件使用主要涉及三个类
-  
SpringRedisLockClient 用于 操作 redis 的客户端 ,底层是通过 spring-data-redis 的 RedisTemplate 实现的
 -  
RedisLockService / ZookeeperLockService 分布式锁的Redis/ZK具体实现类
 -  
SharedLockInterceptor 用于支持 AOP 模式的拦截器,这里是可选的,如果不需要支持 拦截器则不用实例化
 
样例:
public class CustomizeSharedLockConfiguration {
  /**
   * 配置 redis 共享锁服务提供者
   * @param redisTemplate
   * @return
   */
  @PostConstruct
  public void defaultSharedLock(RedisTemplate redisTemplate){
    ISharedLockProvider provider = new RedisLockProvider(new RedisLockClient(redisTemplate));
    // 这里 addDecorator classes 可以添加一些支持的特性具体见特性介绍
    SharedLockEnvironment.getInstance().addDecoratorClasses(defaultDecorators()).setDefaultProvder(provider);
  }
  /**
  * zk 共享锁
  */
  @PostConstruct
  public void defaultSharedLock(CuratorFramework zookeeper){
    ISharedLockProvider provider = new ZookeeperLockProvider(new CuratorLockClient(zookeeper));
    // 这里 addDecorator classes 可以添加一些支持的特性具体见特性介绍
    // 同时可以设置全局的 lockSeconds 、 provider(如果有多个)和  zk 的锁父节点
    SharedLockEnvironment.getInstance().setConfigurer(ZookeeperLockProvider.class, ZookeeperConfigurer.builder().namespace("/lock-namespace").build()).addDecoratorClasses(defaultDecorators()).setDefaultProvder(provider);
  }
 /**
   * 创建 共享锁 拦截器
   * @param context
   * @return
   */
  @Bean
  public SharedLockInterceptor annotationSharedLoadInterceptor(ApplicationContext context){
    return new SharedLockInterceptor(context);
  }
} 
 使用说明
组件有三种主要使用方式:
- try/finally 通用方式,一般常用的方式
 - execute callback 回调方式,类似 JdbcTemplate execute
 - AOP + Annotation 模式,类似 spring 事务
 
全局参数配置
对于一些全局性配置,如 锁定时间,zk 的namespace等,可以使用全局配置实例配置
  
  // 全局配置只需要执行一次即可,开发者可以交由 Spring 管理,比如 单例的 PostConstruct 方法等
  // 这里配置了 lockSeconds(锁定时长,如果不设置,则默认20秒), zk 的node 父节点,默认的服务提供者 (默认提供者必须提供)
  SharedLockEnvironment.getInstance().lockSeconds(20).setConfigurer(ZookeeperLockProvider.class, ZookeeperConfigurer.builder().namespace("/lock-namespace").build()).addDecoratorClasses(defaultDecorators()).setDefaultProvder(provider);
   
 SharedLock 组件内部的锁定义了多种状态(定义在net.madtiger.lock.SharedLockStatus 类中)
/**
 * 共享锁状态
 *
 * @author Fenghu.Shi
 * @version 1.2.0
 */
public enum SharedLockStatus {
  /**
   * 新建
   */
  NEW,
  /**
   * 已锁定
   */
  LOCKED,
  /**
   * 获取所超时
   */
  TIMEOUT,
  /**
   * 取消
   */
  CANCEL,
  /**
   * 获取锁成功后,解锁失败,此阶段正常应该回滚
   */
  UNLOCK_FAIL,
  /**
   * 超时后,已解锁
   */
  TIMEOUT_UNLOCK,
  /**
   * 取消后,已解锁
   */
  CANCEL_UNLOCK,
  /**
   * 释放锁成功
   */
  DONE;
} 
 创建锁对象使用 Builder模式
//支持链式操作
ISharedLock lock = SharedLockBuilder.builder(LOCK_KEY).build(); 
 try/finally 使用方式
此方式是以往分布式锁使用较多的方式,需要用户手动获取并释放。
SharedLock 在原始的使用方式基础上增加了一些比较常用/方便的特性
- 实现了 
java.lang.AutoCloseable接口,支持 JDK 8 try-with-resource 特性 - 返回数据支持链式调用
 - 支持意外回滚
 
基础使用方式
见 try/finally 基本使用方式 的 doTry()方法
备注
-  
try-with-resource 和 普通try/finally 使用场景区分 对于所有逻辑都在 try {} 内部执行时,建议使用 try-with-resource 模式,如果需要在 try {} 外部还有根据获取锁状态进行其他业务逻辑时,使用 普通的 try/finally 模式
 -  
fault callback 使用 如果需要对锁释放失败,进行回退处理业务时,可以通过如下方式使用。
 
    // 来一个 try-with-resource 模式
    try(ISharedLock lock = SharedLockBuilder.builder(LOCK_KEY).build()) {
      if (lock.tryLock(5, TimeUnit.SECONDS)) {
        System.out.println("执行成功");
        return Flux.just("OK");
      } else {
        return Flux.error(new Throwable("失败了"));
      }
    }
 
 - rollback 模式 当已经获取了锁,由于执行时间过长或者网络等其他原因导致锁没有释放成功,对于安全性较高的系统,在释放锁失败后,需要回退时,可以通过此机制回退。
 
    
    // 来一个基本模式
    ISharedLock lock = SharedLockBuilder.builder(LOCK_KEY).build();
    Flux<String> result;
    try{
      // 尝试获取所并判断是否锁定成功
      if (lock.tryLock(10, TimeUnit.SECONDS)){
        result = Flux.just("获取锁成功");
      }else {
        result = Flux.just("获取锁失败");
      }
    } finally {
      // 3. 释放锁
      lock.unlock();
      // 这里失败了,咱们 rollback 一下
      if (lock.isRollback()) {
        result = Flux.just("回滚吧");
      }
    }
 
 execute callback 方式
该方式锁的获取和执行都由系统管理,只有当获取所成功后才执行此方法。
使用方式见 execute 使用方式 的 doExecute 方法。
AOP Annotation 方式
此方式最简便,只要在需要锁执行的方法上增加 SharedLock 注解。
使用方式见 AOP 使用方式 的 doAOP 方法。
此方式增加了几个特性
- 支持降级
 
SharedLock#fallbackMethod 的 配置当获取锁失败后的降级方法。 注意:这里降级只正对 获取所失败,如果是业务执行失败,则不会调用此方法,业务失败请 catch 处理。
- 回滚处理
 
如果需要对锁释放失败,进行回退处理业务时,可以通过此方式回滚,如果此函数有返回值,且类型和执行方法相同,且此方法返回的数据不为空,则rollback 执行的返回值会覆盖原数据。 覆盖顺序 rollback -> fallback | callback
- 其他参数
 
见 ISharedLock
特性支持(Decorator/装饰者模式)
系统内部除了基本锁功能,还实现了一些其他特性,如:可重入锁,自旋锁等,此类特性都使用 Decorator 实现。
支持如下:
| 特性名称 | 实现类 | 描述 | 
|---|---|---|
| 自旋锁 | net.madtiger.lock.decorator.SpinLockDecorator | 当使用指定时间段内获取锁时(调用了含有 time和 TimeUnit 参数的方法),如果第一次失败,则会立即获取3次,如果失败,则随机休眠200+(300随机)毫秒后继续执行自旋。此服务 建议 Redis Provider 使用。 | 
| 可重入锁 | net.madtiger.lock.decorator.ReentrantLockDecorator | 同一个线程内可以多次获取同一把锁,默认均支持,可以使用 SharedLockEnvironment.getInstance().clearDecoratorClasses()清空 |  
  
| JVM锁 | net.madtiger.lock.decorator.LocalLockDecorator | 未完成,实验中,先误使用,当有一定概率获取到的锁都是在同一个JVM中(如:节点比较少,5个以内)时,先在jvm中添加一个内存锁,当此锁获取成功后,再正常获取远程锁,解锁同理。 | 
客户端(Provider)配置
客户端可以有自己的配置,我们通过 net.madtiger.lock.provider.IProviderConfigurer来实现并配置, 如:ZK 的 namespace 配置。
1. 全局配置方式:
    SharedLockEnvironment.getInstance().lockSeconds(20).setConfigurer(ZookeeperLockProvider.class, ZookeeperConfigurer.builder().namespace("/lock-namespace").build());
 
 2. 局部方式
    ISharedLock lock = SharedLockBuilder.builder(LOCK_KEY).providerConfigurer(ZookeeperConfigurer.builder().namespace("/lock-namespace")).build();
 
 暂时支持的配置如下:
ZooKeeperProvider
具体实现类net.madtiger.lock.zk.ZookeeperConfigurer,支持的属性。
| 属性 | 描述 | 
|---|---|
| namespace | zk 的共享锁 key 父节点,必须以 / 开头 | 
版本更新
- 1.2.0 发布 (2020-02-14)
 
重构了整体结构,添加了 全局配置和builder等模式
- 1.1.0 发布 (2020-02-12)
 
支持了可重入锁
- 1.0.6 发布 (2020-02-11)
 
支持 zookeeper 的 Zookeeper 客户端(CuratorFramework 待完成..)
- 1.0.0 发布 (2020-02-08)
 
完成了基本框架和 redis 实现。