Redisson 分布式锁的最佳实践
引言
在现代分布式系统中,处理并发问题是至关重要的。分布式锁是解决这类问题的关键工具之一。本文将介绍如何使用 Redisson,一个强大的 Redis 客户端库,来实现高性能、高可用性的分布式锁。
基本概念
分布式锁是多个进程或线程之间协调操作的一种机制。它通常包括三个基本操作:加锁、持有锁、释放锁。Redisson 提供了简单而强大的接口来管理这些操作。
Redisson 分布式锁的使用方法
第一、添加依赖
xml
<!-- redisson分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.5</version>
</dependency>
第二、添加redisson配置类
java
package com.kingmouse.lock;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created with IntelliJ IDEA
*
* @author kingMouse
* @date 2023/11/7 09:50
* Description: redisson配置类 单机和集群自选
*/
@Configuration
public class RedissonConfig {
/**
* 单机模式
* @return RedissonClient
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonSingle() {
// 1.创建配置
Config config = new Config();
// 单机模式
config.useSingleServer()
// 设置地址
.setAddress("redis://127.0.0.1:6379")
// 设置连接数
.setConnectionPoolSize(10);
return Redisson.create(config);
}
/**
* 单机模式
* @return RedissonClient
*/
// @Bean(destroyMethod = "shutdown")
// public RedissonClient redissonCluster() {
// // 1.创建配置
// Config config = new Config();
// // 集群模式-带密码
// config.useClusterServers()
// .addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001")
// .setPassword("12356")
// // 设置master节点最小连接数
// .setMasterConnectionMinimumIdleSize(10)
// // 设置slave节点最小连接数
// .setSlaveConnectionMinimumIdleSize(10);
// return Redisson.create(config);
// }
第三、添加测试类
java
package com.kingmouse.lock;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.TimeUnit;
/**
* Created with IntelliJ IDEA
*
* @author kingMouse
* @date 2023/11/5 09:54
* Description:
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedissonLockTest {
@Autowired
private RedissonClient redissonClient;
/**
* 阻塞式
*/
@Test
public void testRedissonBlock() {
// 设置锁的名字,同一个名字为同一把锁,不同名字之间互不干扰
String lockKey = "kingmouse-001";
// 获取锁对象
RLock lock = redissonClient.getLock(lockKey);
// 加锁,尝试获取锁,5秒后获取不到则直接放弃
lock.lock(5, TimeUnit.SECONDS);
// 实际业务处理
try {
System.out.println("阻塞式--加锁成功,线程 ID:" + Thread.currentThread().getId());
} catch (Exception e) {
log.error("业务处理异常:{}", e.getMessage());
} finally {
// 解锁
lock.unlock();
System.out.println("阻塞式--解锁成功,线程 ID:" + Thread.currentThread().getId());
}
}
@Test
public void testRedissonNonBlock() throws Exception {
// 设置锁的名字,同一个名字为同一把锁,不同名字之间互不干扰
String lockKey = "kingmouse-002";
// 获取锁对象
RLock lock = redissonClient.getLock(lockKey);
// 尝试获取锁,最长等待5秒,持有锁最长30秒
boolean isLock = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (isLock) {
// 实际业务处理
try {
System.out.println("非阻塞式--获取锁成功,线程 ID:" + Thread.currentThread().getId());
} catch (Exception e) {
log.error("业务处理异常:{}", e.getMessage());
} finally {
// 解锁
lock.unlock();
System.out.println("非阻塞式--解锁成功,线程 ID:" + Thread.currentThread().getId());
}
}
}
}
测试结果
log
阻塞式--加锁成功,线程 ID:1
阻塞式--解锁成功,线程 ID:1
log
非阻塞式--获取锁成功,线程 ID:1
非阻塞式--解锁成功,线程 ID:1
扩展知识
redisson锁中lock方法和tryLock方法有什么区别
在 Redisson 中,lock 方法和 tryLock 方法都是用来获取分布式锁的,但它们在获取锁的方式和行为上有一些区别:
在 Redisson 中,lock
方法和 tryLock
方法都是用来获取分布式锁的,但它们在获取锁的方式和行为上有一些区别:
-
lock
方法:lock
方法是阻塞的,即如果获取锁失败,调用该方法的线程会被阻塞,直到获取到锁为止。- 如果在获取锁时发生网络分区或其他异常,会一直重试获取锁,直到获取成功或者达到设置的超时时间为止。
- 使用
lock
方法时,如果获取锁成功后,需要手动调用unlock
方法来释放锁。
例如:
java
RLock lock = redisson.getLock("myLock");
lock.lock();
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
-
tryLock
方法:tryLock
方法是非阻塞的,即如果获取锁失败,方法会立即返回,不会阻塞当前线程。tryLock
方法可以设置超时时间,在指定的超时时间内尝试获取锁,如果超时仍未获取到锁,则返回false
。- 使用
tryLock
方法时,获取锁成功后不需要手动释放锁,因为锁会在超时时间后自动释放。(建议使用完自动释放锁)
例如:
java
RLock lock = redisson.getLock("myLock");
boolean isLocked = lock.tryLock(10, TimeUnit.SECONDS);
if (isLocked) {
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
} else {
// 获取锁失败的处理逻辑
}
总结来说,lock
方法是阻塞的,会一直尝试获取锁直到成功,而 tryLock
方法是非阻塞的,在指定的超时时间内尝试获取锁,如果超时则返回失败。选择使用哪种方法取决于你的业务需求,以及是否能够容忍等待获取锁的阻塞时间。
锁续约
使用看门狗(Watchdog)可以实现锁的自动续约:
java
String permitId = lock.acquire(10, TimeUnit.SECONDS);
RScheduledExecutorService executorService = redisson.getExecutorService("myExecutorService");
executorService.schedulePermitExpiration(permitId, 5, TimeUnit.SECONDS);
在这个例子中,我们使用 schedulePermitExpiration
方法每隔5秒续约一次锁的持有时间。
注意事项
在使用分布式锁时,需要注意以下几点:
-
合理设置锁的持有时间:不要将锁的持有时间设置得过长,以免影响系统的并发性能。根据实际业务需求,选择一个合适的持有时间。
-
使用看门狗进行续约:在需要锁的持有时间较长的情况下,使用看门狗可以确保锁不会在持有时间内过期,避免了手动续约的繁琐操作。
-
异常处理:在加锁、续约、释放锁的过程中,需要考虑可能出现的异常情况,并进行适当的处理,以确保锁能够被正确释放,避免死锁等问题。
实际应用场景
在电商系统中,分布式锁可以用来避免商品超卖问题。在任务调度系统中,可以确保同一个任务在同一时刻只被一个节点执行,避免任务重复执行等问题。
总结
Redisson 提供了简单而强大的分布式锁解决方案,通过合理设置锁的持有时间和使用看门狗进行续约,可以确保系统在高并发环境下的稳定性和可靠性。在实际应用中,开发人员可以根据业务需求选择合适的锁策略,并进行必要的异常处理,以实现分布式系统的高效运行。