redis实现分布式锁
通过唯一标识值和原子操作,保证了锁的基本原子性和持有者的唯一性。Watchdog 自动续期确保任务执行超时时不会因锁过期被其他线程抢占。Redisson 提供了更高的易用性和可靠性,非常适合在 Spring Boot 中集成。
·
Redis 分布式锁是一个轻量级的分布式锁实现方式,利用 Redis 提供的原子性和高性能特性,来保证分布式环境下资源的独占访问。
一、Redis 分布式锁原理
Redis 分布式锁的实现主要依赖于以下几个关键步骤:
-
加锁(SETNX + EXPIRE):使用
SETNX
(Set if Not Exists)命令实现加锁。如果键不存在则创建,表示加锁成功。- 通常使用唯一标识作为锁的值,以便后续解锁时能够验证锁的持有者。
- 为防止因进程崩溃导致锁永久占用,设置一个过期时间(
EXPIRE
),锁在超时后自动释放。
-
释放锁(DEL):当加锁的操作完成后,持有锁的客户端执行解锁。
- 为了避免误删锁的情况(例如其他客户端误删除当前锁),通常在删除前会校验锁的唯一标识。
- 验证值与锁的持有者匹配,确保解锁的是同一客户端。
-
锁自动续期(可选):为确保锁的安全性和保持稳定的锁持有状态,可以设置自动续期机制。
示例:使用 SET NX PX
原子命令
SET lock_key unique_value NX PX 30000
lock_key
:锁的键名unique_value
:用于唯一标识的值NX
:保证在键不存在时才设置PX 30000
:设置锁的过期时间为 30 秒
二、Redis 分布式锁存在的漏洞
-
锁的误删除问题:
- 当加锁客户端的任务超时未完成,锁过期释放,而其他客户端重新获取了锁。此时,第一个客户端如果未完成任务,就可能误删他人的锁。
- 解决方案:通过唯一标识来删除锁,确保删除的是自己持有的锁。
-
锁的超时释放:
- 当任务执行时间超过锁的过期时间时,锁会自动释放,导致锁的控制失效,其他客户端也可能获取到锁。
- 解决方案:在任务执行期间,进行自动续约操作,以防止锁过期释放。
-
Redis 主从一致性问题:
- Redis 是异步复制,锁的写入可能未同步到从节点,在主节点故障后,切换到从节点时,锁的数据可能丢失。
- 解决方案:使用 Redis 官方提供的分布式锁算法 Redlock 或使用 Redis 集群模式。
三、Spring Boot 集成 Redisson 实现分布式锁
Redisson 是一个 Redis 的高级客户端,支持分布式锁、同步器等常用的分布式工具,能够简化分布式锁的操作。
3.1 添加依赖
在 pom.xml
中添加 Redisson 依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.7</version>
</dependency>
3.2 配置 Redisson 客户端
在 application.yml
文件中添加 Redis 配置:
spring:
redis:
host: localhost
port: 6379
配置 Redisson 的配置类:
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import org.redisson.Redisson;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
config.setCodec(new StringCodec()); // 使用 String 编码
return Redisson.create(config);
}
}
3.3 使用 Redisson 实现分布式锁
在业务逻辑中使用 Redisson 的分布式锁实现资源控制。
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class MyService {
@Autowired
private RedissonClient redissonClient;
public void executeTaskWithLock() {
// 获取分布式锁对象
RLock lock = redissonClient.getLock("myLock");
try {
// 尝试获取锁,并设置等待时间与锁的超时时间
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
try {
// 执行需要加锁的逻辑
System.out.println("Lock acquired, executing protected code.");
Thread.sleep(2000); // 模拟业务逻辑
} finally {
// 确保锁在执行后释放
lock.unlock();
}
} else {
System.out.println("Unable to acquire lock, another process is running.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Task interrupted");
}
}
}
tryLock
参数说明:- 第一个参数
10
表示等待时间,在等待时间内如果获取到锁就执行,否则放弃。 - 第二个参数
30
表示锁的持有时间,在此时间内没有释放会自动释放。 - 注意:确保在业务逻辑执行完后释放锁,以免锁被长期占用。
- 第一个参数
3.4 使用定时续期机制(Watchdog)
Redisson 提供了 “看门狗” 自动续期机制,默认持锁 30 秒。只要持锁的客户端不断地续期,锁将一直有效,直到显式释放。
public void executeTaskWithWatchdog() {
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock(); // 默认 30s 超时时间,Redisson 自动续期
System.out.println("Lock acquired with watchdog, executing protected code.");
Thread.sleep(2000); // 执行业务逻辑
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock(); // 显式释放锁
}
}
四、总结
使用 Redis 分布式锁实现并发控制是一个高效的解决方案,Redisson 提供了良好的封装,支持简单使用分布式锁以及复杂的 Watchdog 自动续期机制:
- 通过唯一标识值和原子操作,保证了锁的基本原子性和持有者的唯一性。
- Watchdog 自动续期确保任务执行超时时不会因锁过期被其他线程抢占。
- Redisson 提供了更高的易用性和可靠性,非常适合在 Spring Boot 中集成。
更多推荐
所有评论(0)