Lok4j 简介
lock4j是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。
立志打造一个简单但富有内涵的分布式锁组件。
特点
- 简单易用,功能强大,扩展性强。
- 支持redission,redisTemplate,zookeeper。可混用,支持扩展
Docker 安装Redis
请参考文章:Docker 安装Redis
Docker 安装ZooKeeper
请参考文章:Docker 安装Zookeeper
SpringBoot 集成Lock4j
Lock4j 之Reids 版本
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringBootCase</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>SpringBoot-Lock4J</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<mybatisplus-spring-boot-starter.version>3.4.3</mybatisplus-spring-boot-starter.version>
<mysql-spring-boot-starter.version>8.0.19</mysql-spring-boot-starter.version>
</properties>
<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>
</project>
application.yml 之Redis 配置
server:
port: 8086
spring:
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:
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
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
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);
}
}
Lock4j 之Redission版本
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringBootCase</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>SpringBoot-Lock4JRedission</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--若使用redisson作为分布式锁底层,则需要引入-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
</project>
application.yml 配置文件
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 注解,实现分布式锁功能
package cn.zzg.lock4j.controller;
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);
}
}
Lock4j 之ZooKeeper 版本
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringBootCase</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>SpringBoot-Lock4jZookeeper</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--添加开源分布式锁Lock4j-->
<!--若使用Zookeeper作为分布式锁底层,则需要引入-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
</project>
application.yml 之zookeeper配置
server:
port: 8087
spring:
coordinate:
zookeeper:
zkServers: 192.168.43.10:2181
controller 使用Lock4j 注解,实现分布式锁功能
package cn.zzg.lock.controller;
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")
@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 注解详解
Lock4J 高级使用
1、配置获取锁超时时间和锁过期时间。
lock4j:
acquire-timeout: 3000 #默认值3s,可不设置
expire: 30000 #默认值30s,可不设置
primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置
acquire-timeout /排队时长,超出这个时间退出队列,并抛出超时异常。
expire 锁过期时间 。 主要是防止死锁。默认30秒是为了兼容绝大部分场景。
2、自定义执行器。
前提条件必须继承抽象类:com.baomidou.lock.executor.AbstractLockExecutor<T>
ZooKeeper 版本执行器之ZookeeperLockExecutor
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.lock.executor;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZookeeperLockExecutor extends AbstractLockExecutor<InterProcessMutex> {
private static final Logger log = LoggerFactory.getLogger(ZookeeperLockExecutor.class);
private final CuratorFramework curatorFramework;
public InterProcessMutex acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
if (!CuratorFrameworkState.STARTED.equals(this.curatorFramework.getState())) {
log.warn("instance must be started before calling this method");
return null;
} else {
String nodePath = "/curator/lock4j/%s";
try {
InterProcessMutex mutex = new InterProcessMutex(this.curatorFramework, String.format(nodePath, lockKey));
boolean locked = mutex.acquire(acquireTimeout, TimeUnit.MILLISECONDS);
return (InterProcessMutex)this.obtainLockInstance(locked, mutex);
} catch (Exception var10) {
return null;
}
}
}
public boolean releaseLock(String key, String value, InterProcessMutex lockInstance) {
try {
lockInstance.release();
return true;
} catch (Exception var5) {
log.warn("zookeeper lock release error", var5);
return false;
}
}
public ZookeeperLockExecutor(final CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
}
}
Redis 版本执行器之RedisTemplateLockExecutor
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.lock.executor;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
public class RedisTemplateLockExecutor extends AbstractLockExecutor<String> {
private static final Logger log = LoggerFactory.getLogger(RedisTemplateLockExecutor.class);
private static final RedisScript<String> SCRIPT_LOCK = new DefaultRedisScript("return redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2])", String.class);
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";
private final StringRedisTemplate redisTemplate;
public String acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
String lock = (String)this.redisTemplate.execute(SCRIPT_LOCK, this.redisTemplate.getStringSerializer(), this.redisTemplate.getStringSerializer(), Collections.singletonList(lockKey), new Object[]{lockValue, String.valueOf(expire)});
boolean locked = "OK".equals(lock);
return (String)this.obtainLockInstance(locked, lock);
}
public boolean releaseLock(String key, String value, String lockInstance) {
String releaseResult = (String)this.redisTemplate.execute(SCRIPT_UNLOCK, this.redisTemplate.getStringSerializer(), this.redisTemplate.getStringSerializer(), Collections.singletonList(key), new Object[]{value});
return Boolean.parseBoolean(releaseResult);
}
public RedisTemplateLockExecutor(final StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
3、自定义Key生成器
前提条件必须实现:com.baomidou.lock.LockKeyBuilder 接口
默认Key生成器:DefaultLockKeyBuilder
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.lock;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;
public class DefaultLockKeyBuilder implements LockKeyBuilder {
private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
private static final ExpressionParser PARSER = new SpelExpressionParser();
private BeanResolver beanResolver;
public DefaultLockKeyBuilder(BeanFactory beanFactory) {
this.beanResolver = new BeanFactoryResolver(beanFactory);
}
public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
Method method = invocation.getMethod();
return definitionKeys.length <= 1 && "".equals(definitionKeys[0]) ? "" : this.getSpelDefinitionKey(definitionKeys, method, invocation.getArguments());
}
protected String getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
StandardEvaluationContext context = new MethodBasedEvaluationContext((Object)null, method, parameterValues, NAME_DISCOVERER);
context.setBeanResolver(this.beanResolver);
List<String> definitionKeyList = new ArrayList(definitionKeys.length);
String[] var6 = definitionKeys;
int var7 = definitionKeys.length;
for(int var8 = 0; var8 < var7; ++var8) {
String definitionKey = var6[var8];
if (definitionKey != null && !definitionKey.isEmpty()) {
String key = (String)PARSER.parseExpression(definitionKey).getValue(context, String.class);
definitionKeyList.add(key);
}
}
return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
}
}
4、自定义锁获取失败策略
前提条件必须实现:com.baomidou.lock.LockFailureStrategy 接口
默认失败策略:DefaultLockFailureStrategy
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.lock;
import com.baomidou.lock.exception.LockFailureException;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultLockFailureStrategy implements LockFailureStrategy {
private static final Logger log = LoggerFactory.getLogger(DefaultLockFailureStrategy.class);
protected static String DEFAULT_MESSAGE = "request failed,please retry it.";
public DefaultLockFailureStrategy() {
}
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);
}
//结束
}
}
参考资料
官网地址:Lock4J