Redis集群方式共有三种:主从模式,哨兵模式,cluster(集群)模式
其中主从模式会保证数据在从节点还有一份,但是主节点挂了之后,需要手动把从节点切换为主节点。它非常简单,但是在实际的生产环境中是很少使用的。
哨兵模式就是主从模式的升级版,该模式下会对响应异常的主节点进行主观下线或者客观下线的操作,并进行主从切换。它可以保证高可用。
cluster (集群)模式保证的是高并发,整个集群分担所有数据,不同的 key 会放到不同的 Redis 中。每个 Redis 对应一部分的槽。
(上面三种模式也是面试重点,可以说很多道道出来,由于不是本文重点就不详细描述了。主要表达的意思是你得在面试的时候遇到相关问题,需要展示自己是知道这些东西的,都是面试的套路。)
在上面描述的集群模式下还是会出现一个问题,由于节点之间是采用异步通信的方式。如果刚刚在 Master 节点上加了锁,但是数据还没被同步到 Salve。这时 Master 节点挂了,它上面的锁就没了,等新的 Master 出来后(主从模式的手动切换或者哨兵模式的一次 failover 的过程),就可以再次获取同样的锁,出现一把锁被拿到了两次的场景。
锁都被拿了两次了,也就不满足安全性了。一个安全的锁,不管是不是分布式的,在任意一个时刻,都只有一个客户端持有。
Redisson 实现(单机部署时)
Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。
Redisson 中的分布式锁自带自动续期机制,它提供了一个专门用来监控锁的 Watch Dog( 看门狗),如果操作共享资源的还未完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。
我这里以 Redisson 的分布式可重入锁 RLock 为例来说明如何使用 Redisson 实现分布式锁:
// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁,具有 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();
可以看出,代码非常简洁直观。
如果使用 Redis 来实现分布式锁的话,还是比较推荐直接基于 Redisson 来做的。
分布式Redis下的加锁--RedLock
为了避免单点故障,生产环境下的 Redis 服务通常是集群化部署的。
Redis 集群下,上面介绍到的分布式锁的实现会存在一些问题。由于 Redis 集群数据同步到各个节点时是异步的,如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。
针对这个问题,Redis 之父 antirez 设计了 Redlock 算法open in new window 来解决。
Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。
即使部分 Redis 节点出现问题,只要保证 Redis 集群中有半数以上的 Redis 节点可用,分布式锁服务就是正常的。
Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
Redlock 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。《数据密集型应用系统设计》一书的作者 Martin Kleppmann 曾经专门发文怼过 Redlock,他认为这是一个很差的分布式锁实现。感兴趣的朋友可以看看Redis 锁从面试连环炮聊到神仙打架open in new window这篇文章,有详细介绍到 antirez 和 Martin Kleppmann 关于 Redlock 的激烈辩论。
实际项目中不建议使用 Redlock 算法,成本和收益不成正比。
如果不是非要实现绝对可靠的分布式锁的话,其实单机版 Redis 就完全够了,实现简单,性能也非常高。如果你必须要实现一个绝对可靠的分布式锁的话,可以基于 Zookeeper 来做,只是性能会差一些。
发表回复