【Redis分布式锁实现】基于 Redis 单节点(Spring Boot 示例)
下面我将详细介绍基于 Redis 单节点实现分布式锁的原理,并提供一个完整的 Spring Boot 实现示例。
实现原理
核心机制
原子获取锁:使用
SET key unique_value NX PX milliseconds命令NX:仅当 key 不存在时设置值PX:设置过期时间(毫秒)unique_value:唯一标识客户端(防止误删其他客户端的锁)
安全释放锁:使用 Lua 脚本保证原子性
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
锁续期机制:可选的看门狗(Watchdog)机制,定期延长锁的有效期
关键特性
- 互斥性:同一时刻只有一个客户端能持有锁
- 防死锁:自动过期机制确保锁最终释放
- 容错性:客户端崩溃后锁会自动释放
- 安全性:只有锁的持有者才能释放锁
Spring Boot 实现示例
1. 添加依赖 (pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
2. 配置 Redis (application.yml)
spring:
redis:
host: localhost
port: 6379
password:
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
3. Redis 分布式锁工具类
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
private final RedisTemplate<String, String> redisTemplate;
// 锁键前缀
private static final String LOCK_PREFIX = "lock:";
// 解锁Lua脚本
private static final String UNLOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
// 续期Lua脚本
private static final String RENEW_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('pexpire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁的key
* @param requestId 请求标识(可使用UUID)
* @param expireTime 锁的过期时间(毫秒)
* @param waitTime 等待时间(毫秒)
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime, long waitTime) {
String fullKey = LOCK_PREFIX + lockKey;
long end = System.currentTimeMillis() + waitTime;
while (System.currentTimeMillis() < end) {
// 尝试获取锁
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(fullKey, requestId, expireTime, TimeUnit.MILLISECONDS);
if (Boolean.TRUE.equals(success)) {
return true;
}
// 等待随机时间后重试,避免活锁
try {
Thread.sleep(50 + (long) (Math.random() * 100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 释放分布式锁
*
* @param lockKey 锁的key
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean unlock(String lockKey, String requestId) {
String fullKey = LOCK_PREFIX + lockKey;
// 使用Lua脚本保证原子性
RedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(fullKey), requestId);
return result != null && result == 1;
}
/**
* 锁续期(看门狗机制)
*
* @param lockKey 锁的key
* @param requestId 请求标识
* @param expireTime 新的过期时间(毫秒)
* @return 是否续期成功
*/
public boolean renewLock(String lockKey, String requestId, long expireTime) {
String fullKey = LOCK_PREFIX + lockKey;
// 使用Lua脚本保证原子性
RedisScript<Long> script = new DefaultRedisScript<>(RENEW_SCRIPT, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(fullKey),
requestId,
String.valueOf(expireTime));
return result != null && result == 1;
}
/**
* 获取锁(简化版,带自动续期)
*
* @param lockKey 锁的key
* @param expireTime 锁的过期时间(毫秒)
* @param waitTime 等待时间(毫秒)
* @param task 需要执行的任务
* @return 任务执行结果
*/
public <T> T lockAndExecute(String lockKey, long expireTime, long waitTime, LockTask<T> task) {
String requestId = UUID.randomUUID().toString();
boolean locked = false;
try {
// 尝试获取锁
locked = tryLock(lockKey, requestId, expireTime, waitTime);
if (!locked) {
throw new RuntimeException("获取分布式锁失败");
}
// 启动看门狗线程定期续期
WatchDog watchDog = new WatchDog(lockKey, requestId, expireTime);
watchDog.start();
try {
// 执行业务逻辑
return task.execute();
} finally {
// 停止看门狗
watchDog.stop();
}
} finally {
// 确保锁被释放
if (locked) {
unlock(lockKey, requestId);
}
}
}
// 看门狗线程实现
private class WatchDog {
private final String lockKey;
private final String requestId;
private final long expireTime;
private volatile boolean running = true;
private Thread thread;
public WatchDog(String lockKey, String requestId, long expireTime) {
this.lockKey = lockKey;
this.requestId = requestId;
this.expireTime = expireTime;
}
public void start() {
thread = new Thread(() -> {
// 在过期时间的1/3时进行续期
long sleepTime = expireTime / 3;
while (running) {
try {
Thread.sleep(sleepTime);
if (!renewLock(lockKey, requestId, expireTime)) {
// 续期失败,可能是锁已被释放或过期
break;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "RedisLock-WatchDog");
thread.setDaemon(true);
thread.start();
}
public void stop() {
running = false;
if (thread != null) {
thread.interrupt();
}
}
}
// 锁任务接口
@FunctionalInterface
public interface LockTask<T> {
T execute();
}
}
4. 业务服务中使用分布式锁
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final RedisDistributedLock redisLock;
public OrderService(RedisDistributedLock redisLock) {
this.redisLock = redisLock;
}
/**
* 创建订单(使用分布式锁保护)
*/
public void createOrder(String orderId) {
// 使用锁执行关键操作
redisLock.lockAndExecute("order_create:" + orderId, 30000, 5000, () -> {
// 在这里执行需要加锁的业务逻辑
try {
// 1. 检查订单是否已存在
if (checkOrderExists(orderId)) {
throw new RuntimeException("订单已存在");
}
// 2. 执行创建订单的核心业务
processOrderCreation(orderId);
// 3. 记录订单日志
logOrderCreation(orderId);
return null;
} catch (Exception e) {
throw new RuntimeException("订单创建失败", e);
}
});
}
private boolean checkOrderExists(String orderId) {
// 实际业务逻辑
return false;
}
private void processOrderCreation(String orderId) {
// 实际业务逻辑
System.out.println("处理订单创建: " + orderId);
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void logOrderCreation(String orderId) {
// 实际业务逻辑
System.out.println("记录订单日志: " + orderId);
}
}
5. 控制器示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/order/{orderId}")
public String createOrder(@PathVariable String orderId) {
try {
orderService.createOrder(orderId);
return "订单创建成功: " + orderId;
} catch (Exception e) {
return "订单创建失败: " + e.getMessage();
}
}
}
关键注意事项
锁过期时间:
- 设置合理的时间(略大于业务执行时间)
- 过短:业务未完成锁已释放 → 数据不一致
- 过长:客户端崩溃后锁释放延迟 → 系统可用性降低
唯一标识(requestId):
- 必须保证全局唯一(使用UUID)
- 确保只有锁的持有者才能释放锁
看门狗机制:
- 解决业务执行时间超过锁过期时间的问题
- 定期续期(建议在1/3过期时间时续期)
- 业务完成后立即停止看门狗
异常处理:
- 使用try-finally确保锁最终被释放
- 避免因异常导致锁无法释放
重试机制:
- 设置合理的等待时间和重试策略
- 使用随机退避避免活锁
潜在缺陷及解决方案
| 缺陷 | 解决方案 |
|---|---|
| 锁提前过期 | 实现看门狗续期机制 |
| 非原子操作风险 | 使用Lua脚本保证原子性 |
| 单点故障 | 主从复制(但有数据丢失风险)或改用RedLock |
| GC暂停导致锁失效 | 优化JVM参数,减少GC暂停时间 |
| 时钟漂移问题 | 使用NTP同步时间,监控时钟差异 |
| 锁被误删 | 使用唯一标识验证锁持有者 |
最佳实践建议
- 锁粒度:尽量使用细粒度锁(如订单ID而非整个系统锁)
- 超时设置:根据业务压力动态调整锁超时时间
- 监控报警:监控锁等待时间、获取失败率等关键指标
- 熔断机制:当Redis不可用时提供降级方案
- 压力测试:模拟高并发场景验证锁的正确性
- 避免长时间持锁:优化业务逻辑减少锁持有时间
这个实现提供了生产环境中使用Redis分布式锁的完整解决方案,包含了基本的锁获取/释放、看门狗续期机制、以及易用的API封装。在实际使用中,可以根据具体业务需求调整参数和实现细节。
【Redis分布式锁实现】基于 Redis 单节点(Spring Boot 示例)的更多相关文章
- Redis分布式锁,基于StringRedisTemplate和基于Lettuce实现setNx
使用redis分布式锁,来确保多个服务对共享数据操作的唯一性一般来说有StringRedisTemplate和RedisTemplate两种redis操作模板. 根据key-value的类型决定使用哪 ...
- 基于zookeeper实现分布式锁和基于redis实现分布所的区别
1,实现方式不同 zookeeper实现分布式锁:通过创建一个临时节点,创建的成功节点的服务则抢占到分布式锁,可做业务逻辑.当业务逻辑完成,连接中断,节点消失,继续下一轮的锁的抢占. redis实现分 ...
- C# Redis分布式锁(基于ServiceStack.Redis)
相关的文章其实不少,我也从中受益不少,但是还是想自己梳理一下,毕竟自己写的更走心! 首先给出一个拓展类,通过拓展方法实现加锁和解锁. 注:之所以增加拓展方法,是因为合理使用拓展类(方法),可以让程序更 ...
- 七种方案!探讨Redis分布式锁的正确使用姿势
前言 日常开发中,秒杀下单.抢红包等等业务场景,都需要用到分布式锁.而Redis非常适合作为分布式锁使用.本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式.如果有不正确的地方,欢迎大家 ...
- Redis分布式锁 (图解-秒懂-史上最全)
文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...
- 手撕redis分布式锁,隔壁张小帅都看懂了!
前言 上一篇老猫和小伙伴们分享了为什么要使用分布式锁以及分布式锁的实现思路原理,目前我们主要采用第三方的组件作为分布式锁的工具.上一篇运用了Mysql中的select ...for update实现了 ...
- python redis分布式锁改进
0X01 python redis分布式锁通用方法 REDIS分布式锁实现的方式:SETNX + GETSET 使用Redis SETNX 命令实现分布式锁 python 版本实现上述思路(案例1) ...
- 基于Redis分布式锁(获取锁及解锁)
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency).可用性( ...
- Dubbo入门到精通学习笔记(八):ActiveMQ的安装与使用(单节点)、Redis的安装与使用(单节点)、FastDFS分布式文件系统的安装与使用(单节点)
文章目录 ActiveMQ的安装与使用(单节点) 安装(单节点) 使用 目录结构 edu-common-parent edu-demo-mqproducer edu-demo-mqconsumer 测 ...
- C# Redis分布式锁(RedLock) - 多节点
Redis单节点的分布式锁只需要注意三点就可以了: 1.加锁并设置锁的过期时间必须是原子操作; 2.锁的value值必须要有唯一性; 3.释放锁的时候要验证其value值,不是自己加的锁不能释放. 但 ...
随机推荐
- 在 .NET 中使用 Sqids 快速的为数字 ID 披上神秘短串,轻松隐藏敏感数字!
前言 在当今数字化时代,数据的安全性和隐私性至关重要.随着网络应用的不断发展,数字 ID 作为数据标识和访问控制的关键元素,其保护显得尤为重要.然而,传统的数字 ID 往往直接暴露了一些敏感信息,如顺 ...
- Vue3 学习-初识体验-helloworld
在数据分析中有一个最重要的一环就是数据可视化, 数据报表的开发. 从我从业这几年的经历上看, 经历了从业务系统导表格数据, 到Excel+PPT, 再是开源报表工具, 再是主流商业BI产品(低/零代码 ...
- 网络编程:理解TCP中的“流”
TCP是一种流式协议 TCP数据是流式的特性,可分别从发送端和接收端来阐述 发送端:当调用send函数完成数据"发送"后,数据并没有真正从网络上发送出去,只是从应用程序拷贝到了操作 ...
- 1、Java相关工具下载及准备
相关准备 oracle jdk8:oracle官网. IDEA:官网 Maven:官网,下第二个 Redis:windows, MySQL:官网归档, RabbitMQ:csdn Java环境配置 变 ...
- 转|如何从 100 亿 URL 中找出相同的 URL
题目描述 给定 a.b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G.请找出 a.b 两个文件共同的 URL. 解答思路 每个 URL 占 64B,那么 50 亿 ...
- FFmpeg开发笔记(六十二)Windows给FFmpeg集成H.266编码器vvenc
<FFmpeg开发实战:从零基础到短视频上线>该书的第八章介绍了如何在Windows环境给FFmpeg集成H.264和H.265的编码器,如今H.266的编码器vvenc也日渐成熟,从7 ...
- 浅谈RMI、JRMP、JNDI
目录 RMI 概念: 为什么要有RMI? RMI的构成: 如何使用RMI 注意!!! JRMP(是RMI的通信协议的名字) 概念 查看通信过程 工具使用 攻击Server 攻击Client JNDI ...
- 【中英】【吴恩达课后测验】Course 5 - 序列模型 - 第一周测验
[中英][吴恩达课后测验]Course 5 -序列模型 - 第一周测验 - 循环神经网络 上一篇:[课程4 - 第四周编程作业]※※※※※ [回到目录]※※※※※下一篇:[待撰写-课程5 -第一周编程 ...
- 【Zookeeper从入门到实战】SpringBoot整合完整指南
Zookeeper从入门到实战:SpringBoot整合完整指南 一.Zookeeper概述 1.1 什么是Zookeeper Zookeeper是一个开源的分布式协调服务,由Apache软件基金会维 ...
- JVM 类加载过程与字节码执行深度解析
在 Java 高级程序员面试中,类加载机制与字节码执行原理是 JVM 模块的核心考察点.本文从类加载生命周期.类加载器协作机制.字节码执行引擎及面试高频问题四个维度,结合 JVM 规范与 HotSpo ...