RedisTemplate 分布式锁实现详解

一、核心实现原理

RedisTemplate 分布式锁基于 Redis 的原子操作实现,核心流程如下:

  1. 加锁 :通过 SET key value NX PX timeout命令原子化设置锁(NX表示键不存在时设置,PX设置过期时间)。

  2. 解锁:通过 Lua 脚本保证删除锁的原子性(验证锁持有者身份后删除)。

  3. 续期:通过后台线程定期延长锁的过期时间,防止业务执行超时导致锁提前释放。


二、完整代码实现

1. 依赖配置(Maven)

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. RedisTemplate 配置

复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

3. 分布式锁工具类

复制代码
@Component
public class RedisDistributedLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE = 30_000; // 默认30秒

    // 加锁(带UUID防误删)
    public String tryLock(String lockKey, long expire) {
        String uuid = UUID.randomUUID().toString();
        Boolean success = redisTemplate.opsForValue().setIfAbsent(
            LOCK_PREFIX + lockKey, 
            uuid, 
            expire, 
            TimeUnit.MILLISECONDS
        );
        return success != null && success ? uuid : null;
    }

    // 解锁(Lua脚本保证原子性)
    public boolean unlock(String lockKey, String uuid) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "   return redis.call('del', KEYS[1]) " +
                        "else " +
                        "   return 0 " +
                        "end";
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Arrays.asList(LOCK_PREFIX + lockKey),
            uuid
        );
        return result != null && result == 1L;
    }

    // 续期(看门狗机制)
    public boolean renewLock(String lockKey, String uuid, long expire) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "   return redis.call('pexpire', KEYS[1], ARGV[2]) " +
                        "else " +
                        "   return 0 " +
                        "end";
          Long result= redisTemplate.execute(
                    new DefaultRedisScript<>(script, Long.class),
                    Arrays.asList(LOCK_PREFIX + lockKey),
                    uuid, expire
            ) ;
            return result!= null && result == 1L;
    }
}

三、使用示例

1. 业务代码集成

复制代码
@Service
public class OrderService {
    @Autowired
    private RedisDistributedLock lockUtil;

    public void createOrder() {
        String lockKey = "order:create";
        String lockValue = lockUtil.tryLock(lockKey, 10_000); // 10秒过期

        if (lockValue != null) {
            try {
                // 执行业务逻辑(如扣减库存)
                System.out.println("处理订单中...");
                Thread.sleep(5_000); // 模拟耗时操作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lockUtil.unlock(lockKey, lockValue); // 释放锁
            }
        } else {
            System.out.println("获取锁失败,稍后重试");
        }
    }
}

2. 自动续期(可选)

复制代码
// 启动续期线程(需结合定时任务或线程池)
public void startRenewalTask(String lockKey, String uuid, long expire) {
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    scheduler.scheduleAtFixedRate(() -> {
        if (lockUtil.renewLock(lockKey, uuid, expire)) {
            System.out.println("锁续期成功");
        } else {
            System.out.println("锁已释放或续期失败");
        }
    }, expire / 3, expire / 3, TimeUnit.MILLISECONDS); // 每1/3过期时间续期一次
}

四、关键优化点

1. 防误删锁

  • UUID 标识:每个锁绑定唯一 UUID,解锁时验证持有者身份,避免 A 线程释放 B 线程的锁。

  • Lua 脚本 :通过原子化脚本确保 GETDEL操作的原子性。

2. 高并发优化

  • 自旋重试:加锁失败时短暂休眠后重试,避免频繁阻塞线程。

    复制代码
    public String tryLockWithRetry(String lockKey, long expire, int retryTimes) {
        for (int i = 0; i < retryTimes; i++) {
            String uuid = tryLock(lockKey, expire);
            if (uuid != null) return uuid;
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        }
        return null;
    }

3. 性能优化

  • 连接池配置:使用 Lettuce 连接池提升吞吐量。

    复制代码
    spring:
      redis:
        lettuce:
          pool:
            max-active: 50
            max-idle: 10
            min-idle: 2

五、注意事项

  1. 锁过期时间:需大于业务执行时间,建议设置业务时间的 2-3 倍。

  2. Redis 高可用:单节点 Redis 存在单点故障风险,建议使用 Redis Cluster 或 Redlock 算法。

  3. 异常处理 :确保 finally块中释放锁,避免锁泄漏。

  4. 监控指标:监控锁的获取成功率、平均持有时间、续期频率等。


六、与 Redisson 对比

特性 RedisTemplate 手动实现 Redisson
开发成本 高(需手动处理续期、异常等) 低(封装完善)
功能完整性 基础功能(需自行扩展) 支持可重入锁、公平锁、看门狗等
性能 依赖代码优化 高度优化
适用场景 需要定制化锁策略的项目 快速实现标准分布式锁的场景

七、总结

通过 RedisTemplate 实现分布式锁需重点关注 原子性操作 ​ 和 锁生命周期管理。核心步骤包括:

  1. 使用 setIfAbsent原子化加锁。

  2. 通过 Lua 脚本安全解锁。

  3. 可选续期机制防止业务超时。

对于复杂场景(如可重入锁、公平锁),建议直接使用 Redisson 库以减少开发成本。

相关推荐
武藤一雄3 小时前
C#:进程/线程/多线程/AppDomain详解
后端·微软·c#·asp.net·.net·wpf·.netcore
武藤一雄12 小时前
C# Prism框架详解
开发语言·后端·微软·c#·.net·wpf
wniuniu_14 小时前
ceph基础知识
ceph·wpf
DataIntel15 小时前
WPF 操作之Dispatcher--- 只在多线程更新 UI 时使用。
wpf
Macbethad15 小时前
WPF工业设备远程控制程序技术方案
分布式·wpf
Macbethad1 天前
工业设备数据记录程序技术方案
wpf·信息与通信
zzyzxb2 天前
WPF 中隧道事件和冒泡事件
wpf
闲人编程2 天前
API限流、鉴权与监控
分布式·python·wpf·限流·集群·令牌·codecapsule
TA远方2 天前
【WPF】桌面程序使用谷歌浏览器内核CefSharp控件详解
wpf·浏览器·chromium·控件·cefsharp·cefsharp.wpf