Spring Boot - 实用功能19 - Lock4j分布式锁门面

Lock4j分布式锁门面


lock4j是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。

立志打造一个简单但富有内涵的分布式锁组件。

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

一:Docker 安装 redis

1:docker 查找 redis 并进行拉取操作

docker search redis

docker pull redis
docker pull redis:[指定的版本号]

2:docker 查找本地镜像文件 redis

# 查看redis的容器
docker images
 
# 如果不满意当前版本,可以通过以下方式删除
docker rmi IMAGE_ID

3:创建Redis 挂载文件目录和redis.conf 配置文件。==> 切换至/usr/local目录

# 创建redis文件夹
mkdir -p docker/redis

# 创建redis本地数据文件夹
mkdir docker/redis/data

# 创建redis的redis.conf文件
cd docker/redis

# 确保在redis文件夹下
touch redis.conf

4:基于Redis 版本,修改redis.conf 相关配置。

# ================= 端口和连接 ===============
port 6379 # Redis服务器监听的端口号,默认为6379。
tcp-backlog 511 # TCP连接的backlog队列长度,即在内核中等待处理的连接数
bind 0.0.0.0 # 绑定到所有IPv4地址,允许任何IP地址连接到Redis服务器
timeout 0 # 关闭客户端连接的超时时间,0表示禁用
tcp-keepalive 0 # TCP keepalive的空闲时间,0表示禁用

# ================== 日志和数据库 ==================
loglevel notice # 日志级别,notice是较详细的日志级别之一
logfile "" # 日志文件名,空字符串表示不写入文件
databases 16 # 数据库的数量,默认为16个

# ================= RDB持久化 ==============
save 900 1
save 300 10
save 60 10000 # 这三个是RDB持久化策略,指定在多少秒内至少完成多少次写操作后,触发一次快照保存
stop-writes-on-bgsave-error yes # 在bgsave error的时候是否停止写入
rdbcompression yes # RDB文件是否压缩
rdbchecksum yes # 在存储和加载RDB文件时,是否校验其完整性。
dbfilename dump.rdb # RDB文件的名称
dir ./ # RDB文件和AOF文件的存储目录

# ============== redis主从复制相关 ================
slave-serve-stale-data yes # 从服务器是否向客户端提供旧数据,当与主服务器失去连接时
slave-read-only yes # 从服务器是否只读
repl-diskless-sync-delay 5 # 无盘复制时,延迟同步的秒数
repl-disable-tcp-nodelay no # 是否禁用TCP_NODELAY选项
slave-priority 100 # 从服务器的优先级,用于选举主服务器时

# =============== AOF 持久化 =================
appendonly no # 是否开启AOF持久化
appendfilename "appendonly.aof" # AOF文件的名称
appendfsync everysec # AOF持久化策略,每秒写入磁盘一次
no-appendfsync-on-rewrite no # 在AOF重写时,是否禁用fsync
auto-aof-rewrite-percentage 100 # AOF文件大小增长多少百分比时触发重写
auto-aof-rewrite-min-size 64mb # AOF文件重写触发的最小文件大小
aof-load-truncated yes # 如果AOF文件被截断,是否加载它
aof-rewrite-incremental-fsync yes # AOF重写时是否使用增量fsync

# ================ 其他配置 =====================
maxheap 51200000
heapdir ./
lua-time-limit 5000 # Lua脚本的最大执行时间(毫秒)
slowlog-max-len 128 # 慢查询日志的最大长度
latency-monitor-threshold 0 # 延迟监控的阈值,0表示禁用
notify-keyspace-events "" # keyspace事件通知的配置
hll-sparse-max-bytes 3000 # HyperLogLog稀疏表示的最大字节数
activerehashing yes # 是否使用活动重哈希
# 设置不同类型客户端输出缓冲区的限制
client-output-buffer-limit normal 0 0 0 
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10 # Redis服务器每秒执行命令的周期数。
daemonize no # redis服务在后台运行,与docker中的-d参数冲突。

# ================= 不同数据结构的ziplist编码的最大条目数,最大值大小
hash-max-ziplist-entries 512 
hash-max-ziplist-value 64

list-max-ziplist-entries 512
list-max-ziplist-value 64
 
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

5:启动docker容器

docker run \
-p 6379:6379 \
--name redis \
-v /docker/redis/redis.conf:/etc/redis/redis.conf \
-v /docker/redis/data:/data \ # 挂载到本地目录
--restart=always \ # 总是允许重启
-d \ 
redis:latest \
redis-server /etc/redis/redis.conf

6:错误排查

查看Redis 容器实例的错误日志:docker logs --tail 50 redis

docker logs --tail 50 redis

Bad directive or wrong number of arguments
 
*** FATAL CONFIG FILE ERROR (Redis 6.2.6) ***
Reading the configuration file, at line 1
>>> 'edis configuration file example.'
Bad directive or wrong number of arguments
 
*** FATAL CONFIG FILE ERROR (Redis 6.2.6) ***
Reading the configuration file, at line 1
>>> 'edis configuration file example.'
Bad directive or wrong number of arguments
 
*** FATAL CONFIG FILE ERROR (Redis 6.2.6) ***
Reading the configuration file, at line 1
>>> 'edis configuration file example.'
Bad directive or wrong number of arguments
 
*** FATAL CONFIG FILE ERROR (Redis 6.2.6) ***
Reading the configuration file, at line 1
>>> 'edis configuration file example.'
Bad directive or wrong number of arguments

造成此错误的原因是:redis.conf 配置文件与redis 版本不匹配。解决办法:找到Redis 正确版本,在官网拷贝指定redis.conf 文件。

Redis 官网地址:https://github.com/redis/redis

二:Docker 安装 Zookeeper

1:检索并拉取指定的镜像

[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              0e901e68141f        2 months ago        142MB
mysql               5.7                 2a0961b7de03        2 months ago        462MB
minio/minio         latest              e31e0721a96b        7 months ago        406MB
rabbitmq            management          6c3c2a225947        7 months ago        253MB
elasticsearch       7.6.2               f29a1ee41030        2 years ago         791MB
delron/fastdfs      latest              8487e86fc6ee        4 years ago         464MB
[root@localhost ~]# docker search zookeeper
NAME                               DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
zookeeper                          Apache ZooKeeper is an open-source server wh…   1258                [OK]
wurstmeister/zookeeper                                                             168                                     [OK]
jplock/zookeeper                   Builds a docker image for Zookeeper version …   165                                     [OK]
bitnami/zookeeper                  ZooKeeper is a centralized service for distr…   77                                      [OK]
mesoscloud/zookeeper               ZooKeeper                                       73                                      [OK]
digitalwonderland/zookeeper        Latest Zookeeper - clusterable                  23                                      [OK]
debezium/zookeeper                 Zookeeper image required when running the De…   17                                      [OK]
 
[root@localhost ~]# docker pull zookeeper:latest
latest: Pulling from library/zookeeper
a2abf6c4d29d: Pull complete
2bbde5250315: Pull complete
202a34e7968e: Pull complete
4e4231e30efc: Pull complete
707593b95343: Pull complete
b070e6dedb4b: Pull complete
46e5380f3905: Pull complete
8b7e330117e6: Pull complete
Digest: sha256:2c8c5c2db6db22184e197afde13e33dad849af90004c330f20b17282bcd5afd7
Status: Downloaded newer image for zookeeper:latest

2:创建ZooKeeper 挂载目录(数据挂载目录、配置挂载目录和日志挂载目录)

[root@localhost ~]# mkdir -p /usr/local/zookeeper/data
[root@localhost ~]# mkdir -p /usr/local/zookeeper/conf
[root@localhost ~]# mkdir -p /usr/local/zookeeper/logs

3:启动ZooKeeper容器

docker run -d \
--name zookeeper \
--privileged=true \
-p 2181:2181 \ # 端口映射
--restart=always \
-v /usr/local/zookeeper/data:/data \
-v /usr/local/zookeeper/conf:/conf \
-v /usr/local/zookeeper/logs:/datalog \ # 挂载
zookeeper

4:添加ZooKeeper配置文件

在挂载配置文件目录(/user/local/zookeeper/conf)下,新增zoo.cfg 配置文件,配置内容如下:

# 是ZooKeeper中使用的基本时间单位,以毫秒为单位。它用于执行心跳检测、会话超时等时间相关的操作。
# 这里设置为2000毫秒(即2秒),意味着ZooKeeper中的所有时间间隔都将基于这个值来计算。
tickTime=2000

# 参数用于限制跟随者(follower)服务器连接到领导者(leader)服务器并进行数据同步的初始化时间长度,以tickTime的倍数来表示。
# 在这个例子中,initLimit=10意味着跟随者服务器有20秒(因为tickTime=2000毫秒)的时间来连接到领导者服务器并完成初始化数据同步。
# 如果在这段时间内未能完成,则跟随者服务器将被认为是死掉的,并从集群中移除
initLimit=10

# 参数定义了跟随者服务器与领导者服务器之间同步操作的时间限制,也是以tickTime的倍数来表示。
# 在这个例子中,syncLimit=5意味着跟随者服务器有10秒(因为tickTime=2000毫秒)的时间来与领导者服务器保持同步。
# 如果跟随者服务器在10秒内未能与领导者服务器保持同步(例如,因为网络延迟或高负载),则它将被认为是与集群不同步的
# 但不会被立即从集群中移除
syncLimit=5

# 参数指定了ZooKeeper服务器存储快照(snapshot)和事务日志(transaction logs)的目录。
# 在这个例子中,ZooKeeper的数据将存储在/data目录下。
# 这个目录需要在ZooKeeper服务器启动之前就已经存在,并且ZooKeeper服务进程需要有足够的权限来读写这个目录
dataDir=/data

# 参数指定了ZooKeeper服务器监听的端口,客户端通过这个端口与ZooKeeper服务器进行通信。
# 在这个例子中,ZooKeeper服务器将监听2181端口。
# 这个端口是ZooKeeper的默认端口,但你可以根据需要将其更改为其他端口。
clientPort=2181

5:安装ZooInspector客户端连接

下载地址:https://so.youkuaiyun.com/so/search?q=jira&spm=1001.2101.3001.7020)/secure/attachment/12436620/ZooInspector.zip

命令:java -jar zookeeper-dev-ZooInspector.jar

遇到的问题:WARNING: IPv4 forwarding is disabled. Networking will not work

解决方法:vi /etc/sysctl.conf -> 添加net.ipv4.ip_forward=1 -> 重启systemctl restart network

三:SpringBoot 集成 Lock4j

0:注解属性说明

属性说明
name需要锁住的Key的名称
executor可以通过该参数设置自定义特定的执行器
keys需要锁住的key的名称,可以是多个
expire锁过期的时间,防止死锁
acquireTimeout可以理解为排队等待的时长,超过这个时长就退出排队,并排除获取锁超时异常
autoRelease是否自动释放锁,默认是true

1:Lock4j 之 Redis 版本

<dependencies>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatisplus-spring-boot-starter.version}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql-spring-boot-starter.version}</version>
    </dependency>
    <!--添加开源分布式锁Lock4j-->
    <!--若使用redisTemplate作为分布式锁底层,则需要引入-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
        <version>2.2.4</version>
    </dependency>
</dependencies>
server:
  port: 8086
spring:
  # mysql数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.43.10:3306/bill?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
    username: root
    password: 123456
    hikari:
      minimum-idle: 10
      maximum-pool-size: 20
      idle-timeout: 500000
      max-lifetime: 540000
      connection-timeout: 60000
      connection-test-query: select 1
  # redis
  redis:
    database: 0
    host: 192.168.43.10
    port: 6379
    jedis:
      pool: # jedis连接池
        max-active: 200
        max-wait: -1
        max-idle: 10
        min-idle: 0
    timeout: 6000

# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml # 指定mapper位置
  type-aliases-package: cn.zzg.mybatisplus.entity # 指定实体包

controller 使用 Lock4j 注解,实现分布式锁功能

package cn.zzg.lock.controller;
 
import com.baomidou.lock.annotation.Lock4j;
import org.springframework.web.bind.annotation.*;
 
@RestController
@RequestMapping("/lock4j")
public class Lock4JController {
 
    @GetMapping("/lockMethod")
    @Lock4j(keys = {"#key"}, acquireTimeout = 1000, expire = 10000)
    public String lockMethod(@RequestParam String key){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new String(key);
    }
}

2:Lock4j 之 Redission 版本

<!--若使用redisson作为分布式锁底层,则需要引入-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.4</version>
</dependency>
server:
  port: 8089
spring:
  redis:
    database: 0
    host: 192.168.43.10
    port: 6379
    jedis:
      pool:
        max-active: 200
        max-wait: -1
        max-idle: 10
        min-idle: 0
    timeout: 6000

controller 使用 Lock4j 注解,实现分布式锁功能

import com.baomidou.lock.annotation.Lock4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/lock4j")
public class Lock4JController {
 
    @GetMapping("/lockMethod")
    @Lock4j(keys = {"#key"}, acquireTimeout = 1000, expire = 10000)
    public String lockMethod(@RequestParam String key){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new String(key);
    }
}

3:Lock4j 之 ZooKeeper 版本

<!--添加开源分布式锁Lock4j-->
<!--若使用Zookeeper作为分布式锁底层,则需要引入-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
    <version>2.2.4</version>
</dependency>
server:
  port: 8087
spring:
  coordinate:
    zookeeper:
      zkServers: 192.168.43.10:2181 # zk server 的 ip:port

controller 使用 Lock4j 注解,实现分布式锁功能

import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.lock.executor.ZookeeperLockExecutor;
import org.springframework.web.bind.annotation.*;
 
@RestController
@RequestMapping("/lock4j")
public class Lock4JController {
 
    @GetMapping("/lockMethod")
    // 使用的执行器指定为ZookeeperLockExecutor
    @Lock4j(keys = {"#key"}, acquireTimeout = 1000, expire = 10000, executor = ZookeeperLockExecutor.class)
    public String lockMethod(@RequestParam String key){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new String(key);
    }
}

四:Lock4J 高级使用

1:配置获取锁超时时间和锁过期时间

lock4j:
  acquire-timeout: 3000 # 排队时长,超出这个时间退出队列,并抛出超时异常, 默认值3s,可不设置
  expire: 30000 #锁过期时间 。 主要是防止死锁。默认30秒是为了兼容绝大部分场景, 默认值30s,可不设置
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
  lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置

2:自定义执行器

前提条件必须继承抽象类:com.baomidou.lock.executor.AbstractLockExecutor<T>

例如:ZooKeeper 版本执行器之ZookeeperLockExecutor

@Slf4j
@RequiredArgsConstructor
/**
 * 基于ZooKeeper实现的分布式锁执行器。
 * 使用CuratorFramework作为ZooKeeper客户端,实现锁的获取和释放。
 */
public class ZookeeperLockExecutor extends AbstractLockExecutor<InterProcessMutex> {

    private final CuratorFramework curatorFramework;

    /**
     * 尝试获取分布式锁。
     * 
     * @param lockKey 锁的关键字,用于在ZooKeeper中创建锁节点的路径。
     * @param lockValue 锁的值,可以用于进一步标识锁。
     * @param expire 锁的过期时间,未使用。
     * @param acquireTimeout 获取锁的超时时间。
     * @return 如果成功获取锁,返回InterProcessMutex实例;否则返回null。
     */
    @Override
    public InterProcessMutex acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
        // 检查CuratorFramework实例是否已启动
        if (!CuratorFrameworkState.STARTED.equals(curatorFramework.getState())) {
            log.warn("instance must be started before calling this method");
            return null;
        }
        // 构建锁节点的路径
        String nodePath = "/curator/lock4j/%s";
        try {
            // 创建InterProcessMutex实例,并尝试获取锁
            InterProcessMutex mutex = new InterProcessMutex(curatorFramework, String.format(nodePath, lockKey));
            final boolean locked = mutex.acquire(acquireTimeout, TimeUnit.MILLISECONDS);
            // 根据获取锁的结果,返回相应的锁实例或null
            return obtainLockInstance(locked, mutex);
        } catch (Exception e) {
            // 获取锁过程中发生异常,返回null
            return null;
        }
    }

    /**
     * 释放分布式锁。
     * 
     * @param key 锁的关键字,与获取锁时使用的key相同。
     * @param value 锁的值,与获取锁时使用的value相同。
     * @param lockInstance 锁实例,用于释放锁。
     * @return 如果成功释放锁,返回true;否则返回false。
     */
    @Override
    public boolean releaseLock(String key, String value, InterProcessMutex lockInstance) {
        try {
            // 直接释放锁
            lockInstance.release();
        } catch (Exception e) {
            // 释放锁过程中发生异常,记录日志并返回false
            log.warn("zookeeper lock release error", e);
            return false;
        }
        // 成功释放锁,返回true
        return true;
    }
}

例如:Redis 版本执行器之 RedisTemplateLockExecutor

/**
 * Redis模板锁执行器,实现了基于Redis的分布式锁。
 * 使用StringRedisTemplate和Lua脚本实现锁的获取和释放,以提高锁操作的原子性。
 */
@Slf4j
@RequiredArgsConstructor
public class RedisTemplateLockExecutor extends AbstractLockExecutor<String> {

    /**
     * 获取锁的Lua脚本。
     * 该脚本用于尝试以NX选项设置键值对,实现锁的获取。
     */
    private static final RedisScript<String> SCRIPT_LOCK = new DefaultRedisScript<>("return redis.call('set',KEYS[1]," +
            "ARGV[1],'NX','PX',ARGV[2])", String.class);
    
    /**
     * 释放锁的Lua脚本。
     * 该脚本首先验证锁是否由当前持有者释放,如果是,则删除锁。
     */
    private static final RedisScript<String> SCRIPT_UNLOCK = new DefaultRedisScript<>("if redis.call('get',KEYS[1]) " +
            "== ARGV[1] then return tostring(redis.call('del', KEYS[1])==1) else return 'false' end", String.class);
    
    /**
     * 表示锁获取成功的固定字符串。
     */
    private static final String LOCK_SUCCESS = "OK";
    
    /**
     * Redis字符串模板,用于执行Redis操作。
     */
    private final StringRedisTemplate redisTemplate;

    /**
     * 尝试获取锁。
     * 使用Lua脚本在Redis中执行SET命令,尝试获取锁。
     * 
     * @param lockKey     锁的键。
     * @param lockValue   锁的值,通常是一个唯一的标识。
     * @param expire      锁的过期时间,单位为毫秒。
     * @param acquireTimeout  获取锁的超时时间,单位为毫秒。
     * @return 一个表示锁实例的字符串,如果获取锁失败,则为null。
     */
    @Override
    public String acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
        String lock = redisTemplate.execute(SCRIPT_LOCK,
                redisTemplate.getStringSerializer(),
                redisTemplate.getStringSerializer(),
                Collections.singletonList(lockKey),
                lockValue, String.valueOf(expire));
        final boolean locked = LOCK_SUCCESS.equals(lock);
        return obtainLockInstance(locked, lock);
    }

    /**
     * 释放锁。
     * 使用Lua脚本在Redis中执行验证和删除操作,以确保只有锁的持有者才能释放锁。
     * 
     * @param key         锁的键。
     * @param value       锁的值,用于验证锁的所有权。
     * @param lockInstance  表示锁实例的字符串。
     * @return 表示释放锁是否成功的布尔值。
     */
    @Override
    public boolean releaseLock(String key, String value, String lockInstance) {
        String releaseResult = redisTemplate.execute(SCRIPT_UNLOCK,
                redisTemplate.getStringSerializer(),
                redisTemplate.getStringSerializer(),
                Collections.singletonList(key), value);
        return Boolean.parseBoolean(releaseResult);
    }
}

3:自定义Key生成器

前提条件必须实现:com.baomidou.lock.LockKeyBuilder 接口;默认Key生成器:DefaultLockKeyBuilder

/**
 * 默认的锁键构建器,实现LockKeyBuilder接口。
 * 用于根据方法调用和定义的键模板生成锁键。
 */
public class DefaultLockKeyBuilder implements LockKeyBuilder {

    // 使用SPI表达式解析器
    private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    // 使用SPI表达式解析器
    private static final ExpressionParser PARSER = new SpelExpressionParser();

    // 用于解析Bean的上下文解析器
    private BeanResolver beanResolver;

    /**
     * 构造函数,初始化beanResolver。
     * 
     * @param beanFactory Bean工厂,用于解析Bean。
     */
    public DefaultLockKeyBuilder(BeanFactory beanFactory) {
        this.beanResolver = new BeanFactoryResolver(beanFactory);
    }

    /**
     * 根据方法调用和定义的键模板构建锁键。
     * 
     * @param invocation 方法调用信息,包含方法和参数。
     * @param definitionKeys 锁键的定义模板。
     * @return 构建的锁键字符串,如果不需要锁键或模板不满足条件,则返回空字符串。
     */
    @Override
    public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
        Method method = invocation.getMethod();
        if (definitionKeys.length > 1 || !"".equals(definitionKeys[0])) {
            return getSpelDefinitionKey(definitionKeys, method, invocation.getArguments());
        }
        return "";
    }

        /**
     * 根据给定的定义键数组、方法和参数值,生成一个SPEL表达式定义键。
     * 
     * @param definitionKeys 原始的定义键数组,可能包含SPEL表达式。
     * @param method 当前方法对象,用于SPEL上下文。
     * @param parameterValues 方法参数值数组,用于SPEL表达式求值。
     * @return 返回处理后的定义键字符串,多个键以点号连接。
     * 
     * 此方法通过创建一个SPEL求值上下文,解析原始定义键数组中的SPEL表达式,
     * 并将求值结果添加到一个新的列表中。最终,将这个列表转换为一个点分隔的字符串返回。
     * 这个方法的主要作用是支持使用SPEL表达式来动态生成或修改定义键。
     */
    protected String getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
        // 创建一个基于方法的SPEL求值上下文
        StandardEvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, NAME_DISCOVERER);
        // 设置bean解析器,用于SPEL表达式中访问beans
        context.setBeanResolver(beanResolver);
        // 初始化一个列表,用于存储处理后的定义键
        List<String> definitionKeyList = new ArrayList<>(definitionKeys.length);
        // 遍历原始定义键数组
        for (String definitionKey : definitionKeys) {
            // 检查定义键是否非空
            if (definitionKey != null && !definitionKey.isEmpty()) {
                // 解析SPEL表达式,并在当前上下文中求值
                String key = PARSER.parseExpression(definitionKey).getValue(context, String.class);
                // 将求值结果添加到定义键列表中
                definitionKeyList.add(key);
            }
        }
        // 将定义键列表转换为一个点分隔的字符串返回
        return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
    }
}

4:自定义锁获取失败策略

前提条件必须实现:com.baomidou.lock.LockFailureStrategy 接口

默认失败策略:DefaultLockFailureStrategy

// 使用SLF4J进行日志记录
@Slf4j
/**
 * 默认的锁失败处理策略。
 * 当获取锁失败时,此策略将抛出LockFailureException,指示请求已失败并应重试。
 */
public class DefaultLockFailureStrategy implements LockFailureStrategy {

    // 锁失败时的默认错误信息
    protected static String DEFAULT_MESSAGE = "请求失败,请重试。";

    /**
     * 在锁获取失败时执行。
     * 抛出带有默认错误信息的LockFailureException,迫使调用者重试请求。
     *
     * @param key 与锁操作相关的键。
     * @param method 尝试获取锁的方法。
     * @param arguments 传递给方法的参数。
     * @throws LockFailureException 抛出带有默认消息的锁失败异常。
     */
    @Override
    public void onLockFailure(String key, Method method, Object[] arguments) {
        throw new LockFailureException(DEFAULT_MESSAGE);
    }
}

5:手动获取/释放锁

@Service
public class CommonService {
    @Autowired
    private LockTemplate lockTemplate;
 
    public void commonLock(String bizId) {
        // 各种查询操作 不上锁
        // ...
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock(bizId, 30000L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }
        // 获取锁成功,处理业务
        try {
            System.out.println("执行普通方法 , 当前线程:" + Thread.currentThread().getName());
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        //结束
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值