找回密码
 立即注册
首页 业界区 业界 【Redis分布式锁实现】基于 Redis 单节点保姆级教程(Sp ...

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

粒浊 2025-7-28 08:21:49
下面我将详细介绍基于 Redis 单节点实现分布式锁的原理,并提供一个完整的 Spring Boot 实现示例。
实现原理

核心机制


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

    • NX:仅当 key 不存在时设置值
    • PX:设置过期时间(毫秒)
    • unique_value:唯一标识客户端(防止误删其他客户端的锁)

  • 安全释放锁:使用 Lua 脚本保证原子性
    1. if redis.call("get", KEYS[1]) == ARGV[1] then
    2.     return redis.call("del", KEYS[1])
    3. else
    4.     return 0
    5. end
    复制代码
  • 锁续期机制:可选的看门狗(Watchdog)机制,定期延长锁的有效期
关键特性


  • 互斥性:同一时刻只有一个客户端能持有锁
  • 防死锁:自动过期机制确保锁最终释放
  • 容错性:客户端崩溃后锁会自动释放
  • 安全性:只有锁的持有者才能释放锁
Spring Boot 实现示例

1. 添加依赖 (pom.xml)
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         spring-boot-starter-data-redis</artifactId>
  5.     </dependency>
  6.     <dependency>
  7.         <groupId>org.apache.commons</groupId>
  8.         commons-pool2</artifactId>
  9.     </dependency>
  10. </dependencies>
复制代码
2. 配置 Redis (application.yml)
  1. spring:
  2.   redis:
  3.     host: localhost
  4.     port: 6379
  5.     password:
  6.     lettuce:
  7.       pool:
  8.         max-active: 8
  9.         max-idle: 8
  10.         min-idle: 0
  11.         max-wait: -1ms
复制代码
3. Redis 分布式锁工具类
  1. import org.springframework.data.redis.core.RedisTemplate;
  2. import org.springframework.data.redis.core.script.DefaultRedisScript;
  3. import org.springframework.data.redis.core.script.RedisScript;
  4. import org.springframework.stereotype.Component;
  5. import java.util.Collections;
  6. import java.util.UUID;
  7. import java.util.concurrent.TimeUnit;
  8. @Component
  9. public class RedisDistributedLock {
  10.     private final RedisTemplate<String, String> redisTemplate;
  11.    
  12.     // 锁键前缀
  13.     private static final String LOCK_PREFIX = "lock:";
  14.     // 解锁Lua脚本
  15.     private static final String UNLOCK_SCRIPT =
  16.         "if redis.call('get', KEYS[1]) == ARGV[1] then " +
  17.         "   return redis.call('del', KEYS[1]) " +
  18.         "else " +
  19.         "   return 0 " +
  20.         "end";
  21.     // 续期Lua脚本
  22.     private static final String RENEW_SCRIPT =
  23.         "if redis.call('get', KEYS[1]) == ARGV[1] then " +
  24.         "   return redis.call('pexpire', KEYS[1], ARGV[2]) " +
  25.         "else " +
  26.         "   return 0 " +
  27.         "end";
  28.     public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {
  29.         this.redisTemplate = redisTemplate;
  30.     }
  31.     /**
  32.      * 尝试获取分布式锁
  33.      *
  34.      * @param lockKey    锁的key
  35.      * @param requestId  请求标识(可使用UUID)
  36.      * @param expireTime 锁的过期时间(毫秒)
  37.      * @param waitTime   等待时间(毫秒)
  38.      * @return 是否获取成功
  39.      */
  40.     public boolean tryLock(String lockKey, String requestId, long expireTime, long waitTime) {
  41.         String fullKey = LOCK_PREFIX + lockKey;
  42.         long end = System.currentTimeMillis() + waitTime;
  43.         
  44.         while (System.currentTimeMillis() < end) {
  45.             // 尝试获取锁
  46.             Boolean success = redisTemplate.opsForValue()
  47.                 .setIfAbsent(fullKey, requestId, expireTime, TimeUnit.MILLISECONDS);
  48.             
  49.             if (Boolean.TRUE.equals(success)) {
  50.                 return true;
  51.             }
  52.             
  53.             // 等待随机时间后重试,避免活锁
  54.             try {
  55.                 Thread.sleep(50 + (long) (Math.random() * 100));
  56.             } catch (InterruptedException e) {
  57.                 Thread.currentThread().interrupt();
  58.                 return false;
  59.             }
  60.         }
  61.         return false;
  62.     }
  63.     /**
  64.      * 释放分布式锁
  65.      *
  66.      * @param lockKey   锁的key
  67.      * @param requestId 请求标识
  68.      * @return 是否释放成功
  69.      */
  70.     public boolean unlock(String lockKey, String requestId) {
  71.         String fullKey = LOCK_PREFIX + lockKey;
  72.         
  73.         // 使用Lua脚本保证原子性
  74.         RedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
  75.         Long result = redisTemplate.execute(script, Collections.singletonList(fullKey), requestId);
  76.         
  77.         return result != null && result == 1;
  78.     }
  79.     /**
  80.      * 锁续期(看门狗机制)
  81.      *
  82.      * @param lockKey    锁的key
  83.      * @param requestId  请求标识
  84.      * @param expireTime 新的过期时间(毫秒)
  85.      * @return 是否续期成功
  86.      */
  87.     public boolean renewLock(String lockKey, String requestId, long expireTime) {
  88.         String fullKey = LOCK_PREFIX + lockKey;
  89.         
  90.         // 使用Lua脚本保证原子性
  91.         RedisScript<Long> script = new DefaultRedisScript<>(RENEW_SCRIPT, Long.class);
  92.         Long result = redisTemplate.execute(script,
  93.             Collections.singletonList(fullKey),
  94.             requestId,
  95.             String.valueOf(expireTime));
  96.         
  97.         return result != null && result == 1;
  98.     }
  99.     /**
  100.      * 获取锁(简化版,带自动续期)
  101.      *
  102.      * @param lockKey    锁的key
  103.      * @param expireTime 锁的过期时间(毫秒)
  104.      * @param waitTime   等待时间(毫秒)
  105.      * @param task       需要执行的任务
  106.      * @return 任务执行结果
  107.      */
  108.     public <T> T lockAndExecute(String lockKey, long expireTime, long waitTime, LockTask<T> task) {
  109.         String requestId = UUID.randomUUID().toString();
  110.         boolean locked = false;
  111.         
  112.         try {
  113.             // 尝试获取锁
  114.             locked = tryLock(lockKey, requestId, expireTime, waitTime);
  115.             
  116.             if (!locked) {
  117.                 throw new RuntimeException("获取分布式锁失败");
  118.             }
  119.             
  120.             // 启动看门狗线程定期续期
  121.             WatchDog watchDog = new WatchDog(lockKey, requestId, expireTime);
  122.             watchDog.start();
  123.             
  124.             try {
  125.                 // 执行业务逻辑
  126.                 return task.execute();
  127.             } finally {
  128.                 // 停止看门狗
  129.                 watchDog.stop();
  130.             }
  131.         } finally {
  132.             // 确保锁被释放
  133.             if (locked) {
  134.                 unlock(lockKey, requestId);
  135.             }
  136.         }
  137.     }
  138.     // 看门狗线程实现
  139.     private class WatchDog {
  140.         private final String lockKey;
  141.         private final String requestId;
  142.         private final long expireTime;
  143.         private volatile boolean running = true;
  144.         private Thread thread;
  145.         public WatchDog(String lockKey, String requestId, long expireTime) {
  146.             this.lockKey = lockKey;
  147.             this.requestId = requestId;
  148.             this.expireTime = expireTime;
  149.         }
  150.         public void start() {
  151.             thread = new Thread(() -> {
  152.                 // 在过期时间的1/3时进行续期
  153.                 long sleepTime = expireTime / 3;
  154.                
  155.                 while (running) {
  156.                     try {
  157.                         Thread.sleep(sleepTime);
  158.                         if (!renewLock(lockKey, requestId, expireTime)) {
  159.                             // 续期失败,可能是锁已被释放或过期
  160.                             break;
  161.                         }
  162.                     } catch (InterruptedException e) {
  163.                         Thread.currentThread().interrupt();
  164.                         break;
  165.                     }
  166.                 }
  167.             }, "RedisLock-WatchDog");
  168.             thread.setDaemon(true);
  169.             thread.start();
  170.         }
  171.         public void stop() {
  172.             running = false;
  173.             if (thread != null) {
  174.                 thread.interrupt();
  175.             }
  176.         }
  177.     }
  178.     // 锁任务接口
  179.     @FunctionalInterface
  180.     public interface LockTask<T> {
  181.         T execute();
  182.     }
  183. }
复制代码
4. 业务服务中使用分布式锁
  1. import org.springframework.stereotype.Service;
  2. @Service
  3. public class OrderService {
  4.     private final RedisDistributedLock redisLock;
  5.    
  6.     public OrderService(RedisDistributedLock redisLock) {
  7.         this.redisLock = redisLock;
  8.     }
  9.    
  10.     /**
  11.      * 创建订单(使用分布式锁保护)
  12.      */
  13.     public void createOrder(String orderId) {
  14.         // 使用锁执行关键操作
  15.         redisLock.lockAndExecute("order_create:" + orderId, 30000, 5000, () -> {
  16.             // 在这里执行需要加锁的业务逻辑
  17.             try {
  18.                 // 1. 检查订单是否已存在
  19.                 if (checkOrderExists(orderId)) {
  20.                     throw new RuntimeException("订单已存在");
  21.                 }
  22.                
  23.                 // 2. 执行创建订单的核心业务
  24.                 processOrderCreation(orderId);
  25.                
  26.                 // 3. 记录订单日志
  27.                 logOrderCreation(orderId);
  28.                
  29.                 return null;
  30.             } catch (Exception e) {
  31.                 throw new RuntimeException("订单创建失败", e);
  32.             }
  33.         });
  34.     }
  35.    
  36.     private boolean checkOrderExists(String orderId) {
  37.         // 实际业务逻辑
  38.         return false;
  39.     }
  40.    
  41.     private void processOrderCreation(String orderId) {
  42.         // 实际业务逻辑
  43.         System.out.println("处理订单创建: " + orderId);
  44.         // 模拟耗时操作
  45.         try {
  46.             Thread.sleep(1000);
  47.         } catch (InterruptedException e) {
  48.             Thread.currentThread().interrupt();
  49.         }
  50.     }
  51.    
  52.     private void logOrderCreation(String orderId) {
  53.         // 实际业务逻辑
  54.         System.out.println("记录订单日志: " + orderId);
  55.     }
  56. }
复制代码
5. 控制器示例
  1. import org.springframework.web.bind.annotation.GetMapping;
  2. import org.springframework.web.bind.annotation.PathVariable;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. public class OrderController {
  6.     private final OrderService orderService;
  7.     public OrderController(OrderService orderService) {
  8.         this.orderService = orderService;
  9.     }
  10.     @GetMapping("/order/{orderId}")
  11.     public String createOrder(@PathVariable String orderId) {
  12.         try {
  13.             orderService.createOrder(orderId);
  14.             return "订单创建成功: " + orderId;
  15.         } catch (Exception e) {
  16.             return "订单创建失败: " + e.getMessage();
  17.         }
  18.     }
  19. }
复制代码
关键注意事项


  • 锁过期时间

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

  • 唯一标识(requestId)

    • 必须保证全局唯一(使用UUID)
    • 确保只有锁的持有者才能释放锁

  • 看门狗机制

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

  • 异常处理

    • 使用try-finally确保锁最终被释放
    • 避免因异常导致锁无法释放

  • 重试机制

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

潜在缺陷及解决方案

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


  • 锁粒度:尽量使用细粒度锁(如订单ID而非整个系统锁)
  • 超时设置:根据业务压力动态调整锁超时时间
  • 监控报警:监控锁等待时间、获取失败率等关键指标
  • 熔断机制:当Redis不可用时提供降级方案
  • 压力测试:模拟高并发场景验证锁的正确性
  • 避免长时间持锁:优化业务逻辑减少锁持有时间
这个实现提供了生产环境中使用Redis分布式锁的完整解决方案,包含了基本的锁获取/释放、看门狗续期机制、以及易用的API封装。在实际使用中,可以根据具体业务需求调整参数和实现细节。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册