您的位置:首页 > 汽车 > 时评 > 分布式锁选型 Redis vs Zookeeper

分布式锁选型 Redis vs Zookeeper

2024/11/16 11:55:28 来源:https://blog.csdn.net/weixin_41866717/article/details/141095179  浏览:    关键词:分布式锁选型 Redis vs Zookeeper

分布式锁作为分布式环境下并发控制利器,使用场景广泛。分布式锁通常可利用中间件RedisZookeeper来实现, 例如针对Java语言Redis有Redisson组件, Zk有Curator组件。

  • Redis是一款内存数据库,通常可用来做缓存,由于其执行命令使用单线程,也可以用来实现分布式锁,在集群模式下,Redis提供主从复制和哨兵机制实现高可用性;
  • Zookeeper是一款分布式协调中间件,集群模式下,Zk基于ZooKeeper Atomic Broadcast(ZAB)自实现了共识机制

从其中间件属性就可以看出Redis是偏AP,而Zk是偏CP的。

Distributed Locks with Redis

使用命令SET key value NX PX 30000对某个不存在的key赋值,如果设置成功则过期时间设为30000ms,由于Redis串行执行命令,且该命令的执行是原子的,可以达到分布式锁抢锁的效果,同时在释放锁时使用Lua脚本判断当前线程是否持有该锁,持有则释放锁:

if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

使用Redis单机节点部署时存在单点故障,Redis官方推荐使用红锁算法(RedLock Algorithm)方式确保分布式锁的可用性:

红锁算法下,客户端获取锁的骤入如下:

  1. 获取当前初始时间戳
  2. 获取所有Redis的锁,获取锁的timeout可根据锁本身的过期时长来确定,比如锁10s过期,那么获取锁如果Redis 50ms没响应就算获取失败了,如果获取失败则忽略;
  3. 当全部获取完后,如果客户端获取到锁的数量超过Redis数量的半数 当前时间戳距离初始时间戳的时长小于锁的过期时长,则获取锁成功,成功后锁的有效时长即为之前的时间戳差值;
  4. 如果第3步判定获取锁失败,则把获取成功的锁给主动释放掉。

使用红锁算法仍然会存在如下问题:

比如有3台Redis server,分别为r1,r2,r3,客户端A获取到其中两台的锁(r1,r2),超过半数,获取锁成功,此时r2重启,内存数据丢失,客户端B又来获取同一把锁,此时可以获取到(r2,r3)的锁,超过半数,获取锁成功,这样客户端A和B就同时持有同一把锁。可以打开AOF,当机器重启后也会保留之前的数据,但是AOF的fsync策略默认为1s一次,如果Redis直接宕机,最多会丢失2s的数据,但如果fsync策略该为always也会影响写入性能。一种解决办法是宕机后不立即重启,而是等待一个过期时间后再重启,这样就不会有A和B同时持有同一把锁的情况发生。

除此之外,Martin Kleppman(DDIA作者)对RedLock算法提出反对,之后 Salvatore Sanfilippo 反对了反对。

Martin Kleppman提出RedLock会自动释放锁,当客户端发生类似GC等阻塞的耗时操作时无法保证锁的安全性,须添加fencing token(其实就是类似版本号的乐观锁)保证安全性。
而Salvatore Sanfilippo说Martin Kleppman的case是yy出来的,且分布式锁只是确保资源独占的一种高效的方式(just a weak lock to avoid, most of the times, concurrent accesses for performances reasons),并不应该依赖分布式锁完全确保资源独占。同时有评论指出:但是如果只是为了提升性能,而不是完全依赖分布式锁保证安全性,那其实单机Redis节点即可,没必要上RedLock。

Distributed Locks with Zookeeper

不同于RedLock,Zk集群内部自实现了共识算法,所以客户端直接与其中一台机器交互即可。

Zk基于ZAB协议实现共识机制,集群所有的写操作集中由Leader处理,Leader会广播消息到所有Follow,当ack超过半数时(包含自己的一票)会提交写并通知Follower提交。

Zk使用全局递增的zxid来记录消息进度,如果Leader宕机会在最新消息进度的节点中投票产生新Leader。

可以看到Zk一次写操作涉及多次交互,故写性能没有Redis高。

Conclusion

如果有其他方式兜底保证资源获取的互斥(如mysql更新时的version乐观锁),借助分布式锁是为了提升性能,用单机Redis即可;

如果完全依赖分布式锁保证资源获取的互斥(也不应该完全依赖,还是得加兜底),用Zookeeper。

References

  • Distributed Locks with Redis
  • How to do distributed locking
  • Is Redlock safe?
  • 深入了解Zookeeper核心原理

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com