Zookeeper 和 Redis 哪种更好?
Redis 可以使用 SetNX 这个指令来实现分布式锁,Zookeeper 可以基于同一级 节点的唯一性或者有序节点的特性来实现分布式锁。由于 Redis 的读写性能要比 Zookeeper 更好,在高并发场景中,Zookeeper 实现分布式锁会存在性能瓶颈。
所以我认为 Redis 比 Zookeeper 更好。
关于这个问题,我想从 3 个方面来说:
为什么使用分布式锁?
使用分布式锁的目的,是为了保证同一时间只有一个 JVM 进程可以对共享资源进行操作。
根据锁的用途可以细分为以下两类:
允许多个客户端操作共享资源,我们称为共享锁
这种锁的一般是对共享资源具有幂等性操作的场景,主要是为了避免重复操作共享资源频繁加锁带来的性能开销。
只允许一个客户端操作共享资源,我们成为排他锁
这种锁一般是用在对共享资源操作具有非幂等性操作的场景,也就是需要保证在同一时刻只有一个进程或者线程能够访问这个共享资源。
目前实现分布式锁最常用的中间件是 Redis 和 Zookeeper
第一种,Redis 可以通过两种方式来实现
\1. 利用 Redis 提供的 SET key value NX PX milliseconds 指令,这个指令是设置一个key-value,如果 key 已经存在,则返回 0,否则返回 1,我们基于这个返回值来判断锁的占用情况从而实现分布式锁。
\2. 基于 Redission 客户端来实现分布式锁,Redisson 提供了分布式锁的封装方法,我们只需要调用 api 中的 lock()和 unlock()方法。它帮我们封装锁实现的细 节和复杂度
l redisson 所有指令都通过 lua 脚本执行并支持 lua 脚本原子性执行
l redisson 中有一个 watchdog 的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔 10 秒帮你把 key 的超时时间设为 30s,就算一直持有锁也不会出现key 过期了。“看门狗”的逻辑保证了没有死锁发生。
第二种,基于 ZK 实现分布式锁的落地方案
Zookeeper 实现分布式锁的方法比较多,我们可以使用有序节点来实现,
1、每个线程或进程在 Zookeeper 上的/lock 目录下创建一个临时有序的节点表示去抢占锁,所有创建的节点会按照先后顺序生成一个带有序编号的节点。
2、线程创建节点后,获取/lock 节点下的所有子节点,判断当前线程创建的节点是否是所有的节点的序号最小的。
3、如果当前线程创建的节点是所有节点序号最小的节点,则认为获取锁成功。
4、如果当前线程创建的节点不是所有节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听,当前一个被监听的节点释放锁之后,触发回调通知,从而再次去尝试抢占锁。
两种方案都有各自的优缺点
对于 redis 的分布式锁而言,它有以下缺点:
l 它获取锁的方式简单粗暴,如果获取不到锁,会不断尝试获取锁,比较消耗性 能。
l Redis 是 AP 模型,在集群模式中由于数据的一致性会导致锁出现问题,即便 使用 Redlock 算法来实现,在某些复杂场景下,也无法保证其实现 100%的可靠性。
不过在实际开发中使用 Redis 实现分布式锁还是比较常见,而且大部分场情况下 不会遇到”极端复杂的场景“,更重要的是 Redis 性能很高,在高并发场景中比较合适。
对于 zk 分布式锁而言:
l zookeeper 天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易 用、适合做分布式锁。
l 如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗 较小。
如果要在两者之间做选择,就我个人而言的话,比较推崇 ZK 实现的锁,因为对 于分布式锁而言,它应该符合 CP 模型,但是 Redis 是 AP 模型,所以在这个点上,Zookeeper 会更加合适。