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

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

实现原理

核心机制

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

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

    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)

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)

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms

3. Redis 分布式锁工具类

java 复制代码
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. 业务服务中使用分布式锁

java 复制代码
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. 控制器示例

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

相关推荐
码字的字节18 小时前
ZooKeeper在Hadoop中的协同应用:从NameNode选主到分布式锁实现
hadoop·分布式·zookeeper·分布式锁
鼠鼠我捏,要死了捏2 天前
基于Redisson实现高并发分布式锁性能优化实践指南
性能优化·分布式锁·redisson
white camel2 天前
分布式方案 一 分布式锁的四大实现方式
redis·分布式·zookeeper·分布式锁
马里奥Marioぅ21 天前
Redis主从切换踩坑记:当Redisson遇上分布式锁的“死亡连接“
redis·分布式锁·redisson·故障转移
xujinwei_gingko24 天前
接口幂等性
分布式锁·redisson·接口幂等性
llwszx1 个月前
分布式锁的四种实现方式:从原理到实践
java·分布式·spring·分布式锁
麓殇⊙1 个月前
redisson锁的可重入、可重试、超时续约原理详解
redis·分布式锁
xujinwei_gingko2 个月前
分布式锁-Redisson实现
分布式锁
快乐肚皮2 个月前
Redisson学习专栏(二):核心功能深入学习(分布式锁,分布式集合,原子操作与计数器,事件与监听)
java·分布式·分布式锁·redisson·事件·分布式集合·原子