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 库以减少开发成本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值