下面我将详细介绍基于 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>
- spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- 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脚本保证原子性单点故障主从复制(但有数据丢失风险)或改用RedLockGC暂停导致锁失效优化JVM参数,减少GC暂停时间时钟漂移问题使用NTP同步时间,监控时钟差异锁被误删使用唯一标识验证锁持有者最佳实践建议
- 锁粒度:尽量使用细粒度锁(如订单ID而非整个系统锁)
- 超时设置:根据业务压力动态调整锁超时时间
- 监控报警:监控锁等待时间、获取失败率等关键指标
- 熔断机制:当Redis不可用时提供降级方案
- 压力测试:模拟高并发场景验证锁的正确性
- 避免长时间持锁:优化业务逻辑减少锁持有时间
这个实现提供了生产环境中使用Redis分布式锁的完整解决方案,包含了基本的锁获取/释放、看门狗续期机制、以及易用的API封装。在实际使用中,可以根据具体业务需求调整参数和实现细节。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |