Redis分布式锁你知道几种方式?一文让你搞懂Redis如何实现分布式锁。

为什么要选择Redis?

Redis凭借其高性能、丰富的数据结构和原子操作特性,成为分布式锁的首选方案之一。但不同实现方案在可靠性、性能和复杂度等方面存在显著差异。

一、基础方案:SETNX命令实现

public class SimpleRedisLock {    private Jedis jedis;    private String lockKey;        public SimpleRedisLock(Jedis jedis, String lockKey) {        this.jedis = jedis;        this.lockKey = lockKey;    }        public boolean tryLock() {        Long result = jedis.setnx(lockKey, "locked");        if (result == 1) {            jedis.expire(lockKey, 30); // 设置过期时间            return true;        }        return false;    }        public void unlock() {        jedis.del(lockKey);    }}// 使用示例Jedis jedis = new Jedis("localhost");SimpleRedisLock lock = new SimpleRedisLock(jedis, "order_lock");try {    if (lock.tryLock()) {        // 业务逻辑    }} finally {    lock.unlock();}

问题分析

  1. 非原子操作:setnx和expire非原子操作,可能产生死锁

  2. 锁误删:任何客户端都可以删除锁

  3. 不可重入:同一线程重复获取会失败

二、改进方案:原子SET命令

  • public class AtomicRedisLock {
        private Jedis jedis;
        private String lockKey;
        private String clientId;
    
        public AtomicRedisLock(Jedis jedis, String lockKey) {
            this.jedis = jedis;
            this.lockKey = lockKey;
            this.clientId = UUID.randomUUID().toString();
        }
    
        public boolean tryLock(int expireSeconds) {
            String result = jedis.set(lockKey, clientId, 
                    SetParams.setParams().nx().ex(expireSeconds));
            return "OK".equals(result);
        }
    
        public boolean unlock() {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                            "return redis.call('del', KEYS[1]) " +
                            "else " +
                            "return 0 " +
                            "end";
            Object result = jedis.eval(script, 
                    Collections.singletonList(lockKey), 
                    Collections.singletonList(clientId));
            return result.equals(1L);
        }
    }
    
    // 使用示例
    AtomicRedisLock lock = new AtomicRedisLock(jedis, "payment_lock");
    try {
        if (lock.tryLock(30)) {
            // 处理支付业务
        }
    } finally {
        lock.unlock();
    }

核心改进

  1. 使用原子SET命令:SET key value NX EX

  2. Lua脚本保证删除原子性

  3. 客户端唯一标识防止误删

仍然存在的问题

  • 锁续期困难

  • 单点故障风险

  • 业务超时可能导致锁失效

三、高可用方案:RedLock算法

3.1 RedLock实现原理

public class RedLock {    private List<Jedis> jedisList;    private String lockKey;    private String clientId;    private int quorum;
    public RedLock(List<Jedis> jedisList, String lockKey) {        this.jedisList = jedisList;        this.lockKey = lockKey;        this.clientId = UUID.randomUUID().toString();        this.quorum = jedisList.size() / 2 + 1;    }
    public boolean tryLock(int expireMillis) {        long startTime = System.currentTimeMillis();
        // 第一阶段:尝试获取多数节点锁        int successCount = 0;        for (Jedis jedis : jedisList) {            if (tryAcquire(jedis, expireMillis)) {                successCount++;            }            if ((System.currentTimeMillis() - startTime) > expireMillis) {                break;            }        }
        // 第二阶段:验证锁有效性        if (successCount >= quorum) {            long validityTime = expireMillis - (System.currentTimeMillis() - startTime);            return validityTime > 0;        }
        // 第三阶段:释放已获得的锁        for (Jedis jedis : jedisList) {            release(jedis);        }        return false;    }
    private boolean tryAcquire(Jedis jedis, long expireMillis) {        try {            String result = jedis.set(lockKey, clientId,                    SetParams.setParams().nx().px(expireMillis));            return "OK".equals(result);        } catch (Exception e) {            return false;        }    }
    private void release(Jedis jedis) {        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +                        "return redis.call('del', KEYS[1]) else return 0 end";        jedis.eval(script, Collections.singletonList(lockKey),                 Collections.singletonList(clientId));    }}

部署要求

  • 至少5个独立Redis实例

  • 节点间时钟同步

  • 需要配置合理的超时时间

适用场景

  • 金融交易等对可靠性要求极高的场景

  • 需要跨机房部署的分布式系统

四、生产级方案:Redisson实现

4.1 Redisson分布式锁

// 配置Redisson客户端Config config = new Config();config.useSingleServer()      .setAddress("redis://127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);
// 获取锁对象RLock lock = redisson.getLock("orderLock");
try {    // 尝试加锁,最多等待100秒,锁定后30秒自动解锁    boolean isLock = lock.tryLock(100, 30, TimeUnit.SECONDS);    if (isLock) {        // 业务处理    }} finally {    lock.unlock();}
// 关闭客户端redisson.shutdown();

核心特性

  1. 自动续期机制(Watchdog)

// Watchdog实现原理(简化版)private void renewExpiration() {    Timeout task = commandExecutor.schedule(() -> {        if (redisClient.eval(...)) { // 检查是否仍持有锁            expireAsync(); // 续期            renewExpiration(); // 递归调用        }    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);}
  1. 支持可重入锁

  2. 提供公平锁、联锁(MultiLock)、红锁(RedLock)实现

  3. 完善的故障处理机制

五、方案对比与选型指南

方案

可靠性

性能

复杂度

适用场景

SETNX基础版

★☆☆☆☆

5w+

测试环境、临时任务

原子SET+Lua

★★★☆☆

4w+

中小型业务、非关键路径

RedLock

★★★★☆

2w+

金融级可靠性要求

Redisson

★★★★★

3w+

生产环境首选方案

性能压测数据参考

并发线程数

SETNX方案

Redisson方案

RedLock方案

100

3200 TPS

2800 TPS

1200 TPS

500

4100 TPS

3500 TPS

1500 TPS

1000

4500 TPS

3800 TPS

1800 TPS

六、常见问题解决方案

问题1:锁提前释放

场景:业务处理时间 > 锁过期时间

解决方案

// Redisson自动续期lock.lock(30, TimeUnit.SECONDS); // 看门狗自动续期
// 手动续期(仅Redisson高级用法)lock.expire(60, TimeUnit.SECONDS);

问题2:主从切换数据丢失

解决方案

// 使用RedLock算法RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);redLock.lock();
// 启用WAIT命令(Redis 3.0+)jedis.set(lockKey, value, SetParams.setParams().nx().ex(30));jedis.waitReplicas(1, 5000); // 等待1个副本确认

问题3:GC停顿导致锁失效

应对策略

  1. 监控JVM GC时间

  2. 适当延长锁超时时间

  3. 使用网络心跳检测

总结:

如何选择合适方案?

  1. 初创快速验证:使用Redisson单节点锁

  2. 金融交易场景:RedLock + 5节点集群

  3. 高并发读场景:Redisson读写锁

  4. 定时任务调度:Redisson公平锁

最终建议

  • 生产环境优先使用Redisson

  • 特殊需求可基于Lua脚本扩展

  • 必须进行故障演练:

    • 模拟网络分区

    • 强制主从切换

    • 注入时钟漂移异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值