lock4j是一个分布式锁组件,提供多种不同的支持以满足不同性能和环境的需求。
快速开始
spring boot 项目接入
添加lock starter组件依赖,已上传到maven中央仓库
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
<version>2.2.5</version>
</dependency>
在需要加分布式锁的方法上,添加注解@Klock,例如:
@Service
public class DemoService {
//默认获取锁超时3秒,30秒锁过期
@Lock4j
public void simple() {
//do something
}
//完全配置,支持spel
@Lock4j(keys = {"#user.id", "#user.name"}, expire = 60000, acquireTimeout = 1000)
public User customMethod(User user) {
return user;
}
}
高级用法
配置全局默认的获取锁超时时间和锁过期时间。
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 锁过期时间 。 主要是防止死锁。 建议估计好你锁方法运行时常,正常没有复杂业务的增删改查最多几秒,留有一定冗余,10秒足够。 我们默认30秒是为了兼容绝大部分场景。
自定义执行器。
@Service
public class DemoService {
//可在方法级指定使用某种执行器,若自己实现的需要提前注入到Spring。
@Lock4j(executor = RedissonLockExecutor.class)
public Boolean test() {
return "true";
}
}
自定义锁key生成器。
默认的锁key生成器为 com.baomidou.lock.DefaultLockKeyBuilder 。
@Component
public class MyLockKeyBuilder extends DefaultLockKeyBuilder {
@Override
public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
String key = super.buildKey(invocation, definitionKeys);
// do something
return key;
}
}
自定义锁获取失败策略。
默认的锁获取失败策略为 com.baomidou.lock.DefaultLockFailureStrategy 。
@Component
public class MyLockFailureStrategy implements LockFailureStrategy {
@Override
public void onLockFailure(String key, long acquireTimeout, int acquireCount) {
// write my code
}
}
手动上锁解锁。
@Service
public class ProgrammaticService {
@Autowired
private LockTemplate lockTemplate;
public void programmaticLock(String userId) {
// 各种查询操作 不上锁
// ...
// 获取锁
final LockInfo lockInfo = lockTemplate.lock(userId, 30000L, 5000L, RedissonLockExecutor.class);
if (null == lockInfo) {
throw new RuntimeException("业务处理中,请稍后再试");
}
// 获取锁成功,处理业务
try {
System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));
} finally {
//释放锁
lockTemplate.releaseLock(lockInfo);
}
//结束
}
}
指定时间内不释放锁(限流)
@Service
public class DemoService {
// 用户在5秒内只能访问1次
@Lock4j(keys = {"#user.id"}, acquireTimeout = 0, expire = 5000, autoRelease = false)
public Boolean test(User user) {
return "true";
}
}
Redis 分布式锁是一种利用 Redis 实现的分布式系统中的锁机制,用于在分布式环境下控制多个客户端对共享资源的访问。它通过 Redis 的原子性操作来确保在分布式系统中的不同节点上,同一时刻只有一个客户端能够获取到锁,从而保证了对共享资源的互斥访问。
分布式锁通常在以下情况下使用:
共享资源访问控制: 当多个客户端需要访问共享资源时,为了避免并发访问导致的数据不一致或竞态条件问题,可以使用分布式锁来控制对资源的访问。
防止重复执行: 在分布式系统中,某些操作可能需要保证只能被执行一次,例如定时任务的执行、数据同步等,可以使用分布式锁来确保操作只会被执行一次。
避免缓存击穿: 当缓存中的数据过期时,可能会出现大量并发请求同时访问数据库的情况,为了避免这种情况,可以使用分布式锁来保证只有一个请求可以去数据库中重新加载数据,其他请求等待结果返回。
示例代码1:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
public class RedissonDistributedLock {
private static final String LOCK_KEY = "mylock";
private static final long LOCK_EXPIRE_TIME = 30; // 锁的过期时间,单位秒
private RedisTemplate<String, String> redisTemplate;
private RedissonClient redissonClient;
public RedissonDistributedLock(RedisTemplate<String, String> redisTemplate, RedissonClient redissonClient) {
this.redisTemplate = redisTemplate;
this.redissonClient = redissonClient;
}
public boolean acquireLock() {
RLock lock = redissonClient.getLock(LOCK_KEY);
try {
return lock.tryLock(LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
public void releaseLock() {
RLock lock = redissonClient.getLock(LOCK_KEY);
lock.unlock();
}
public static void main(String[] args) {
// 初始化 RedisTemplate 和 RedissonClient
RedisTemplate<String, String> redisTemplate = // 初始化 RedisTemplate
RedissonClient redissonClient = Redisson.create(); // 初始化 RedissonClient
RedissonDistributedLock lock = new RedissonDistributedLock(redisTemplate, redissonClient);
try {
if (lock.acquireLock()) {
// 成功获取锁后执行业务逻辑
System.out.println("成功获取到锁,执行业务逻辑...");
Thread.sleep(5000); // 模拟业务逻辑执行时间
} else {
// 获取锁失败,处理逻辑
System.out.println("获取锁失败,处理逻辑...");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.releaseLock();
}
}
}
在这个示例中,我们创建了一个 RedissonDistributedLock 类,它接收一个 RedisTemplate 和 RedissonClient 作为参数,并提供了 acquireLock 和 releaseLock 方法来获取和释放锁。在 main 方法中,我们初始化了 RedisTemplate 和 RedissonClient,并使用 RedissonDistributedLock 来实现分布式锁。
示例代码2:
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Collections;
public class RedisDistributedLock {
private static final String LOCK_KEY = "mylock";
private static final String LOCK_VALUE = "locked";
private static final long LOCK_EXPIRE_TIME = 30000; // 锁的过期时间,单位毫秒
private RedisTemplate<String, String> redisTemplate;
public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
// 设置 RedisTemplate 的序列化器,保证 key 和 value 都以字符串的形式存储
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
}
public boolean acquireLock() {
// 使用 Lua 脚本确保原子性操作
String script = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then "
+ "redis.call('pexpire', KEYS[1], ARGV[2]) return true else return false end";
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
Boolean result = redisTemplate.execute(redisScript, Collections.singletonList(LOCK_KEY), LOCK_VALUE, String.valueOf(LOCK_EXPIRE_TIME));
return result != null && result;
}
public void releaseLock() {
// 释放锁
redisTemplate.delete(LOCK_KEY);
}
public static void main(String[] args) {
// 初始化 RedisTemplate
RedisConnectionFactory connectionFactory = // 设置 Redis 连接工厂
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
RedisDistributedLock lock = new RedisDistributedLock(redisTemplate);
try {
if (lock.acquireLock()) {
// 成功获取锁后执行业务逻辑
System.out.println("成功获取到锁,执行业务逻辑...");
Thread.sleep(5000); // 模拟业务逻辑执行时间
} else {
// 获取锁失败,处理逻辑
System.out.println("获取锁失败,处理逻辑...");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.releaseLock();
}
}
}
这段代码使用了 RedisTemplate 来与 Redis 进行交互,通过执行 Lua 脚本来确保获取锁的原子性操作。在 acquireLock 方法中,通过执行 Lua 脚本来设置锁,并设置了锁的过期时间,从而避免了因为程序异常而导致锁永远不释放的情况。在业务逻辑执行完成后,调用 releaseLock 方法来释放锁。