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

    正在检查是否收录...

【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] then return redis.call("del", KEYS[1]) else return 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: 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(); } } } 

关键注意事项

  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封装。在实际使用中,可以根据具体业务需求调整参数和实现细节。

评论

昵称
邮箱
主页