欧阳亮的博客

编程不止是一份工作,还是一种乐趣!!!

分布式锁:ZooKeeper与Redis的区别

在分布式系统中,分布式锁的应用场景是非常广泛的。Redis和ZooKeeper是目前比较常见的实现方案,那它们之间有什么区别呢,我们应该如何选择?

对于ZooKeeper来说,它的会话与服务端是通过心跳保持连接的,当心跳超时客户端会收到链接丢失的事件,通常来说这不是问题,因为ZK的客户端对自动连接。但如果一直连接不上,服务端会在会话有效期之后,将会话置为过期。这里有个很重要的细节,即使会话已经过期了,在重新连接上服务器之前,客户端永远也不知道自己已经过期了,因为ZooKeeper的会话过期是由服务器端触发并通知客户端的。

这样,如果客户端a与ZooKeeper服务器之间的通信长时间断开的话,客户端a会误以为自己并没有过期,只是临时性的链接丢失,但实际上服务端有可能已经将它置为过期,而这时其它与ZooKeeper服务器通信正常的客户端就有可能获取同一个锁(因为服务端在将客户端a置过期的同时会清除它所创建的临时节点),进而造成两个客户端同时访问临界区的资源。

要解决这个问题也不难,在链接丢失和重新建立链接时,客户端都会收到相应的事件通知。我们可以在客户端维护一个状态变量(比如valid),在链接丢失时将它置为false,并在重新建立链接后将它置为true,并在客户端访问临界区的资源代码中不定期的检查valid的值,一旦发生valid值变为false时,就说明链接丢失了,这时就暂停临界区资源的访问,并等待重新建立链接。

这么做挺麻烦的,大大增加了使用锁的复杂性。但是否真需要这么玩也是看具体的场景的:

  1. 如果客户端加锁成功后,需要访问的资源本身是依赖于ZooKeeper服务器的通知的话,那我们就没必要绕这么一个弯路。比如ZooKeeper实现的主-从模式,成功创建master节点的客户端成为master节点,而它的主要作用是监控ZooKeeper的其它节点,进行相应的任务分配。如果发生了链接丢失,在重新建立链接前当前的master不可能接收到来身ZooKeeper服务器的任何通知,即使别的客户端在这时成功了新的master也不会产生问题,因为链接丢失的客户端在这种情况下重新链接后会收到会话过期事件。

  2. 如果客户端加锁成功后,需要访问的资源不依赖于ZooKeeper服务,那我们就不得不绕这么一个弯路了,原因已经在上面分析过了。


使用Redis实现的锁,并不存在这样的问题,因为key并不会因为客户端怎么样而被删除。但它也有麻烦的一面,为了防止客户端长时间阻塞或者故障宕机而导至锁无法释放,我们需要在加锁的时候指定一个过期时间,不过成本确实比ZooKeeper的实现要低很多。


经过以上分析,我们不难得出以下结论:分布式锁的实现使用ZooKeeper还是redis来实现,取决于加锁的目的。

  1. 如果加锁成功后要做的事情,是一个长时性的或者持续性的工作,那么使用ZooKeeper实现应该会更合理,因为我们很难确定过期时间的设置。

  2. 如果加锁成功后要访问的资源,可以在很短的时间内完成(豪秒级或秒级),那么使用Redis更合理,复杂度更低,过期时间的设置也容易。