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);
}
//结束
}
}