SpringBoot强大的分布式锁组件Lock4j,支持多种实现

开发 前端
lock4j是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。底层通过Spring AOP技术实现,而该切面的优先级是最高的,也就是说当你的环境中有多个切面时(如:声明式事务),也不会导致失效问题。

环境:SpringBoot3.2.5

1. 简介

lock4j是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。底层通过Spring AOP技术实现,而该切面的优先级是最高的,也就是说当你的环境中有多个切面时(如:声明式事务),也不会导致失效问题。该组件具有如下2个特性:

  • 简单易用,功能强大,扩展性强。
  • 支持redission,redisTemplate,zookeeper。可混用,支持扩展。

接下来将详细介绍基于Redis的Lock4J使用。

2. 实战案例

2.1 引入依赖

<properties>
  <lock4j.version>2.2.7</lock4j.version>
</properties>
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
  <version>${lock4j.version}</version>
</dependency>

如果你想基于redisson或者是zookeeper实现,那么你只需要引入对应的包即可。

<!-- redisson -->
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<!-- zookeeper -->
<artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
配置文件
spring:
  data:
    redis:
      timeout: 10000
      connectTimeout: 20000
      host: 127.0.0.1
      password: xxxooo
  #如果你是基于zookeeper实现,那么做如下配置
  coordinate:
    zookeeper:
      zkServers: 127.0.0.1:2181,...

进过以上的配置后接下来你就可以通过注解的方式使用分布式锁了。

2.2 基本使用

@Service
public class StorageService {


  private final StorageRepository storageRepository ;
  public StorageService(StorageRepository storageRepository) {
    this.storageRepository = storageRepository ;
  }


  @Lock4j
  public void deductStorage(Long storageId, int count) {
    // TODO
  }
}

使用非常简单,只需要在你需要上锁的方法上添加@Lock4j注解即可。而这里将使用默认行为:默认获取锁超时3秒,30秒锁过期。

自定义锁key

在上面示例中没有自定义@Lock4j注解的任何属性;那么,将会使用默认的key生成方式,上面的代码将生成如下key。

# 前缀 + ":" + 完全限定类名+方法名+# (这里的#是固定的,如果你自定义了key,会在后面继续拼接)
lock4j:com.pack.test.lock4j.StorageServicedeductStorage#

当我们自定义了key后如下:

@Lock4j(key = "#storageId")
public void deductStorage(Long storageId, int count)

key支持Spring SpEL表达式,如上将生成如下的key

# 方法调用storageService.deductStorage(1, 2) ;
lock4j:com.pack.test.lock4j.StorageServicedeductStorage#1

你也可以不使用SpEL表达式。不使用SpEL表达式那么就需要使用如下语法

@Lock4j(keys = "'storageId'")
public void deductStorage(...) {}

没有使用单引号程序将会报错。

设置过期时间

默认获取锁超时3秒,30秒锁过期,可以通过如下属性配置过期时间

@Lock4j(keys = {"#storageId"}, expire = 3000, acquireTimeout = 3000)
public void deductStorage(Long storageId, int count) {}

expire: 锁过期时间(毫秒);注:锁过期时间必须要大于业务处理时间。acquireTimeout: 获取锁超时时间(毫秒)

以上是Lock4j的基本用法;下面将介绍Lock4j其它高级用法。

2.3 高级用法

全局统一配置

lock4j:
  acquire-timeout: 3000 
  expire: 30000
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor
  lock-key-prefix: lock4j

primary-executor:配置加锁的实现方式;默认顺序是:
redisson > redisTemplate > zookeeper;如果你的环境中这3个都引入了,那么就是按照这个顺序,因为定义他们时使用了@Order注解声明顺序。lock-key-prefix:#锁前缀,如上面示例看到的lock4j为默认值。

自定义执行器(加锁)

我们可以通过实现LockExecutor接口定义自己的加锁实现,比如基于MySQL实现。如下:

@Component
public class JdbcLockExecutor implements LockExecutor<String> {


  private final JdbcTemplate jdbcTemplate ;
  public JdbcLockExecutor(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate ;
  }
  @Override
  public String acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
    // TODO; 获取锁
    return null ;
  }
  @Override
  public boolean releaseLock(String key, String value, String lockInstance) {
    // TODO; 释放锁
    return false;
  }
}

注:这里必须要注册为Spring Bean。否则将无法获取。使用如下:

@Lock4j(executor = JdbcLockExecutor.class)
public void deductStorage(Long storageId, int count) {}

加锁时会根据这里配置的executor Class对象获取Spring容器对应的bean对象。

自定义key生成器

Lock4j默认的锁key生成器为DefaultLockKeyBuilder,我们可以通过LockKeyBuilder接口实现自己的key生成方式

@Component
public class PackLockKeyBuilder implements LockKeyBuilder {


  @Override
  public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
    // TODO; 生成key
    return null;
  }
}

注:这里也必须注册为bean对象。使用如下:

@Lock4j(keyBuilderStrategy = PackLockKeyBuilder.class)
public void deductStorage(Long storageId, int count) {}

内部会通过你这里配置的Class对象获取对应的Spring Bean实例对象。

自定义获取锁失败策略

当获取锁失败时如何进行处理?默认实现是DefaultLockFailureStrategy通过自定义LockFailureStrategy,实现自己的逻辑。与上面的套路一样都需要注册为bean。

@Component
public class PackLockFailureStrategy implements LockFailureStrategy {


  @Override
  public void onLockFailure(String key, Method method, Object[] arguments) {
    // TODO
  }
}

使用如下:

@Lock4j(failStrategy = PackLockFailureStrategy.class)
public void deductStorage(Long storageId, int count) {}

注:这里你也可以不指定failStrategy属性,会自动从容器中查找对应的实现Bean。

非注解实现方式(手动加锁/释放锁)

你也可以通过注入LockTemplate对象,由自己来完成加锁和释放锁的动作。

private final LockTemplate lockTemplate ;
  public StorageService(LockTemplate lockTemplate) {
    this.lockTemplate = lockTemplate ;
  }
  public void deductStorage(Long storageId, int count) {
    String key = "xxxx" ;
    final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedisTemplateLockExecutor.class);
    if (null == lockInfo) {
        throw new RuntimeException("业务处理中,请稍后再试") ;
    }
    // 获取锁成功,处理业务
    try {
        // TODO
    } finally {
        // 释放锁
        lockTemplate.releaseLock(lockInfo) ;
    }
  }
}

手动加锁方式,一般适用于需要更细粒度控制锁边界,否则没必要。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2017-01-16 14:13:37

分布式数据库

2018-04-03 16:24:34

分布式方式

2022-04-08 08:27:08

分布式锁系统

2023-01-13 07:39:07

2023-09-04 08:12:16

分布式锁Springboot

2024-11-28 15:11:28

2021-07-13 06:57:12

SpringbootAOP缓存

2019-06-19 15:40:06

分布式锁RedisJava

2021-02-28 07:49:28

Zookeeper分布式

2017-04-13 10:51:09

Consul分布式

2019-02-26 09:51:52

分布式锁RedisZookeeper

2022-01-06 10:58:07

Redis数据分布式锁

2023-08-21 19:10:34

Redis分布式

2021-10-25 10:21:59

ZK分布式锁ZooKeeper

2023-08-27 22:13:59

Redisson分布式缓存

2024-10-09 17:12:34

2022-10-27 10:44:14

分布式Zookeeper

2023-03-01 08:07:51

2018-04-09 09:15:32

数据库DB分布式锁

2023-09-13 09:52:14

分布式锁Java
点赞
收藏

51CTO技术栈公众号