当前位置: 首页 > news >正文

【Redis分布式锁实现】基于 Redis 单节点(Spring Boot 示例)

下面我将详细介绍基于 Redis 单节点实现分布式锁的原理,并提供一个完整的 Spring Boot 实现示例。

实现原理

核心机制

  1. 原子获取锁:使用 SET key unique_value NX PX milliseconds 命令

    • NX:仅当 key 不存在时设置值
    • PX:设置过期时间(毫秒)
    • unique_value:唯一标识客户端(防止误删其他客户端的锁)
  2. 安全释放锁:使用 Lua 脚本保证原子性

    if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
    elsereturn 0
    end
    
  3. 锁续期机制:可选的看门狗(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: localhostport: 6379password: lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-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();}}}// 锁任务接口@FunctionalInterfacepublic 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();}}
}

关键注意事项

  1. 锁过期时间

    • 设置合理的时间(略大于业务执行时间)
    • 过短:业务未完成锁已释放 → 数据不一致
    • 过长:客户端崩溃后锁释放延迟 → 系统可用性降低
  2. 唯一标识(requestId)

    • 必须保证全局唯一(使用UUID)
    • 确保只有锁的持有者才能释放锁
  3. 看门狗机制

    • 解决业务执行时间超过锁过期时间的问题
    • 定期续期(建议在1/3过期时间时续期)
    • 业务完成后立即停止看门狗
  4. 异常处理

    • 使用try-finally确保锁最终被释放
    • 避免因异常导致锁无法释放
  5. 重试机制

    • 设置合理的等待时间和重试策略
    • 使用随机退避避免活锁

潜在缺陷及解决方案

缺陷 解决方案
锁提前过期 实现看门狗续期机制
非原子操作风险 使用Lua脚本保证原子性
单点故障 主从复制(但有数据丢失风险)或改用RedLock
GC暂停导致锁失效 优化JVM参数,减少GC暂停时间
时钟漂移问题 使用NTP同步时间,监控时钟差异
锁被误删 使用唯一标识验证锁持有者

最佳实践建议

  1. 锁粒度:尽量使用细粒度锁(如订单ID而非整个系统锁)
  2. 超时设置:根据业务压力动态调整锁超时时间
  3. 监控报警:监控锁等待时间、获取失败率等关键指标
  4. 熔断机制:当Redis不可用时提供降级方案
  5. 压力测试:模拟高并发场景验证锁的正确性
  6. 避免长时间持锁:优化业务逻辑减少锁持有时间

这个实现提供了生产环境中使用Redis分布式锁的完整解决方案,包含了基本的锁获取/释放、看门狗续期机制、以及易用的API封装。在实际使用中,可以根据具体业务需求调整参数和实现细节。

http://www.wuyegushi.com/news/940.html

相关文章:

  • 2025-07-28 关于js中不写分号会报错的几种情况(Deepseek)
  • 【好题选讲】P1516 青蛙的约会
  • 文件描述符
  • 机器学习常见的任务类型
  • 一期6.文本摘要
  • day06
  • Anime.js - 轻量级JavaScript动画库
  • 无监督通用异常检测方法SEAD解析
  • Metasploit Framework 6.4.76 (macOS, Linux, Windows) - 开源渗透测试框架
  • C#性能优化:为何 x * Math.Sqrt(x) 远胜 Math.Pow(x, 1.5)
  • Metasploit Pro 4.22.8-2025063001 (Linux, Windows) - 专业渗透测试框架
  • Spring AI 框架中如何集成 MCP?
  • 提升效率:AI 知识库在软件开发中的应用
  • Microsoft SQL Server 2022 RTM GDR CU20 (2025 年 7 月更新)
  • SQL Server Management Studio (SSMS) 21 - 微软数据库管理工具
  • 使用Python进行文件操作
  • 两种刻板印象
  • Microsoft SQL Server 2019 RTM GDR CU32 (2025 年 7 月更新)
  • 分享一个 MySQL binlog 分析小工具
  • OI 回忆录
  • 后端开发必备:生产环境异常自动电话通知方案
  • Microsoft SQL Server 2017 RTM GDR CU31 (2025 年 7 月更新)
  • Vue3入门与工程化实践
  • 分块莫队
  • ACME协议
  • Microsoft SQL Server 2016 with SP3 GDR (2025 年 7 月更新)
  • 网络流量中的旗帜狩猎:Wireshark CTF实战手册
  • AC自动机?WA了。。
  • sa后缀数组7.28
  • 哈希 kmp trie树