RedLock-红锁

聊聊分布式锁的正确打开方式------从单点Redis到RedLock

说起来,分布式锁这个话题,我当年第一次遇到的时候还挺懵的。那时候觉得,不就是加个锁嘛,单机环境下synchronized或者ReentrantLock搞定的事,放到分布式环境能有多复杂?

结果一实践才发现,想简单了。

先说说什么是分布式锁

单机器上线程同步的问题,我们可以用JVM内置的锁来解决。但如果服务部署在多台机器上,每台机器的JVM都是独立的,锁只能管自己那一亩三分地。这时候怎么办?

答案是分布式锁------把锁放到一个公共的地方,让所有机器都来这儿抢。

Redis因为读写性能高、语义简洁顺理成章成了最常用的分布式锁载体。用SET key value NX PX timeout这条命令,加锁成功了就是成功了,失败了就是失败了,简单粗暴有效。

单点Redis锁的问题

但是问题来了------Redis挂掉怎么办?

如果只有一台Redis,所有请求都冲着它去。一旦这台Redis因为各种原因(机器故障、Redis进程崩溃、网络抖动导致的主从切换失败......)不可用了,你的分布式锁就直接失效了。业务还在跑,但锁已经没了,各种并发问题随之而来。

这种情况,生产环境真的能忍吗?反正我忍不了。

于是,RedLock横空出世。

RedLock是怎么玩的

RedLock是Redis作者Salvatore Sanfilippo(也就是Antirez)提出的算法,核心思想一句话就能说清楚:别把鸡蛋放在一个篮子里,搞多个独立的Redis节点,锁要在大多数据节点上加成功才算数

通常建议用奇数个节点,比如5个。加锁的时候,客户端会同时向这5个节点发起加锁请求。如果有超过一半的节点(即至少3个)加锁成功,且整个过程耗时没超过锁的过期时间的一半,那才认为加锁成功。

这么做的好处是:即使其中2个节点挂了,剩下3个还在,锁服务依然正常。这就是RedLock的容错性。

来点实际的------Java怎么用

说到实现,Java里用Redisson框架是最省事的。我当年第一次跑通RedLock的时候还挺激动的,终于不用自己写那些乱七八糟的分布式锁逻辑了。

java 复制代码
public class RedLockDemo {
    public static void main(String[] args) {
        Config config = new Config();
        config.useClusterServers()
            .addNodeAddress("redis://127.0.0.1:6379",
                           "redis://127.0.0.1:6380",
                           "redis://127.0.0.1:6381");
        
        RedissonClient redissonClient = Redisson.create(config);
        RedissonRedLock redLock = redissonClient.getRedLock("resource");
        
        try {
            boolean lockAcquired = redLock.tryLock(5, 5000, TimeUnit.MILLISECONDS);
            if (lockAcquired) {
                // 抢到锁了,干活
            } else {
                System.out.println("没抢到,下次再来吧");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (redLock.isLocked()) {
                redLock.unlock();
            }
            redissonClient.shutdown();
        }
    }
}

代码看着挺简洁的,但背后的实现还是值得唠唠。

RedLock背后的实现原理

如果你去看Redisson的源码,会发现RedissonRedLock其实是继承自RedissonMultiLock的。MultiLock就是联锁,可以同时操作多个锁,统一管理。

java 复制代码
public class RedissonRedLock extends RedissonMultiLock {
    @Override
    protected int failedLocksLimit() {
        return locks.size() - minLocksAmount(locks);
    }
    
    @Override
    protected int minLocksAmount(final List<RLock> locks) {
        return locks.size() / 2 + 1;
    }
    
    @Override
    protected long calcLockWaitTime(long remainTime) {
        return Math.max(remainTime / locks.size(), 1);
    }
}

说白了,RedLock就是让多个锁绑定在一起工作。加锁的时候要全部加锁成功才算数,解锁的时候也是一次性全解开。这种"要么全成功要么全失败"的语义,保证了多个锁之间的一致性。

RedLock也有它的烦恼

听起来挺美的,但RedLock也不是银弹,实际使用中有两个比较明显的坑:

1. 性能问题

每次加锁都要等待多个节点响应才行。以前只要跟一个Redis通信,现在要跟5个通信。这个等待时间,在高并发场景下还是挺肉疼的。

2. 并发安全问题------GC停顿

这个问题比较隐蔽,说的是这么个场景:

  1. 客户端A向3个节点发起加锁请求
  2. 节点还没来得及回复,客户端A的GC来了(Stop-The-World,全程停顿)
  3. 因为加锁耗时超过了过期时间,锁自动释放了
  4. 客户端B进来,成功加锁
  5. 客户端A的GC结束,收到之前的响应,误以为自己加锁成功了

好家伙,这时候客户端A和客户端B同时认为自己持有同一把锁,并发问题就这么来了。

关于这个问题,业内讨论了很久,也没有一个完美的解决方案。

现在的RedLock已经被废弃了

因为上面说的这些问题争议比较大,Redisson官方已经废弃了RedLock。官方文档里明确说了不推荐使用。

那该怎么选

如果你的业务需要分布式锁同时又要避免单点故障,其实有更稳妥的方案:

  • ZooKeeper / etcd:用他们天然的分布式锁能力,一致性有保障
  • Consul:同样支持分布式锁
  • Redisson的联锁模式:虽然RedLock废弃了,但MultiLock本身还在,只是需要自己保证节点的高可用

关键是要结合自己的业务场景和技术栈来选型,不要盲目追新。

总结

RedLock作为分布式锁的一种探索,在特定历史时期解决了实际问题。但随着时间推移,大家发现它并不完美。技术选型这事儿,从来都是权衡利弊,没有最好的,只有最适合的。

如果大家在实际项目中用过RedLock或者其他分布式锁方案,欢迎留言交流踩过的坑。

相关推荐
一嘴一个橘子2 小时前
redis 启动
redis
if else2 小时前
Redis 哨兵集群部署方案
数据库·redis
rannn_1113 小时前
【Redis|原理篇2】Redis网络模型、通信协议、内存回收
java·网络·redis·后端·缓存
遇见你的雩风3 小时前
网络原理(一)
java·网络
952363 小时前
Spring IoC&DI
java·数据库·spring
十六年开源服务商3 小时前
游戏与设计驱动WordPress建站2026
java·前端·游戏
前进吧-程序员3 小时前
C++ 内存到底分配在哪?
java·jvm·c++
NWU_白杨3 小时前
VoiceMockInterview项目MVP开发
java·ai
RDCJM4 小时前
Springboot的jak安装与配置教程
java·spring boot·后端