在分布式系统中,分布式锁是为了解决多实例之间的同步问题。例如master选举,能够获取分布式锁的就是master,获取失败的就是slave。又或者能够获取锁的实例能够完成特定的操作。
目前比较常用的分布式锁实现有两种,基于zookeeper实现和基于redis实现。zookeeper和redis也是生产环境中经常用到的第三方组件。下面我会分析它们的实现原理。
实现一个分布式锁至少要满足下面三点要求:
在讲解zookeeper的分布式锁之前有两个概念需要明确:
zookeeper的分布式锁实现原理就是利用临时顺序节点,大概流程为:
例如,对于加锁过程,所有的客户端都在/lock目录下面创建临时节点,如果发现自己创建的临时节点是/lock目录中最小的节点,那么就获取锁成功,否则就watch比自己小的节点中的最大节点。
监控比自己小的节点中的最大节点是为了避免“惊群”效应,避免一个锁释放把所有等待的客户端唤醒,但是只有一个客户端能获取锁。
对于释放锁,只需要把自己创建的临时顺序节点删除即可。整个过程流程图如下:
优点:锁安全性高,zookeeper数据不易丢失。用户使用简单。
缺点:性能消耗比较高。因为需要动态产生和删除临时节点,当集群负载比较高时临时节点消失会有时间差(一般在一分钟范围内)。
redis的分布式锁实现比zookeeper分布式锁实现复杂,也分为redis单实例和多实例(master-master)实现方式。
需要特别指出的是redis如果是master-slave这种结构部署时,获取和释放锁都只能向master请求,和单实例的实现原理基本一样,否则主从切换时会出现多人拿到同一把锁的情况。
例如:
通过下面命令获得锁:
SET resource_name my_random_value NX PX 30000
这个命令的作用是只有这个key不存在时才会设置这个key的值(NX的作用,即not exist),超时时间设置为30000毫秒(PX的作用),这个key的值设置为my_random_value。这个值必须在所有获取锁请求的客户端里面保持唯一。
key值的超时时间,也叫做“锁有效时间”。这是锁的自动释放时间。
这套实现方案在非分布式的、单点的、保证永不宕机的环境是适用的。
在分布式版本的算法里我们假设有N个redis master节点,这些节点完全独立,不用任何的复制或者分布式协调算法来同步数据。
这里假设N=5,一个客户端获取锁的过程如下:
获取锁成功的节点数需要超过master节点数量的一半才认为是获取锁成功的思路应该是借鉴了zookeeper的paxos算法。
还有一个需要指出的点是,当一个客户端获取失败时应该随时延时后再进行重试,避免多个客户端同时重试又同时失败。
优点:性能高
缺点:单实例会有单点问题,多实例主从切换会导致数据丢失,master-master集群模式实现复杂。
redis官方文档 https://redis.io/topics/distlock