了解Zookeeper的我们都知道,Zookeeper是一种分布式协调服务,在分布式应用中,主要用来实现分布式服务的注册与发现以及分布式锁,本文我们简单介绍一下使用Zookeeper实现分布式锁的简单原理。
使用Zookeeper实现分布式锁,主要是基于Zookeeper的临时顺序节点来实现的,因此首先我们先简单了解一下Zookeeper的Znode类型。
1、Zookeeper的Znode类型
Znode分为四种类型:
- 持久化节点(PERSISTENT)
Zookeeper默认的节点类型,该类型的节点创建之后,若客户端与服务端断开连接之前没有执行delete操作,该节点会永久存储于服务端;
- 持久化顺序节点(PERSISTENT_SEQUENTIAL)
该类型的节点创建之后,若客户端与服务端断开连接之前没有执行delete操作,该节点会永久存储于服务端,同时该类型的节点创建之后,Zookeeper会为该类型的节点在节点名称的基础上增加一个单调递增的编号;
- 临时节点(EPHEMERAL)
该类型的节点创建完成之后,若客户端与服务端断开连接之前没有执行delete操作,Zookeeper会自动删除该类型的节点;
- 临时顺序节点
该类型的节点创建完成之后,若客户端与服务端断开连接之前没有执行delete操作,Zookeeper会自动删除该类型的节点,同时该类型的节点创建之后,Zookeeper会为该类型的节点在节点名称的基础上增加一个单调递增的编号;
2、什么是分布式锁
在分布式系统当中,为了防止多个进程(或线程)在对临界资源进行写操作时,造成资源信息的不一致或脏数据的情况发生,需要一个分布式协调技术来对这些分布式的进程(或线程)进行协调,保证多个分布式的进程(或线程)对同一临界资源进行写操作都是同步且有序的,这样的一种分布式协调技术我们就称之为分布式锁。
- 应用场景举例
比如在我们某些电商平台的秒杀活动中,对商品库存进行更新时,就会经常要用到分布式锁。
如上图,假设我们现在有一个商品A,它的总库存是100,客户端发出了4个秒杀请求,每个订单的数量是100个,4个请求到达应用集群后,逻辑处理分别被负载分配到了商品服务A、商品服务B、商品服务C、商品服务D进行处理。
按照正常的业务逻辑,商品库存为100,那么当商品服务A执行完毕并进行商品库存扣减之后,库存就变为0,服务B、C、D执行库存检测时就应该得到库存为0,直接返回库存不足即可。
但是假设我们对商品的库存检查和扣减服务没有做同步处理,那么就有可能出现多个服务同时执行库存扣减的可能性,从而会造成库存的多扣或少扣,对业务造成影响,如果这种情况在电商平台出现,那是相当恐怖的事情。
同时,除了秒杀场景外,账户充值,支付场景中,也都需要用到分布式锁。
- 分布式锁需要具备的条件
- 在分布式环境下,一个方法同一时刻只能被一台机器上的一个线程执行;
- 高可用的获取锁,释放锁;
- 高性能的获取锁,释放锁;
- 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误);
- 具备锁失效机制,避免死锁;
- 具备非阻塞特性,即没有获取到锁将直接返回获取锁失败;
3、Zookeeper分布式锁原理浅析
在对Zookeeper的节点类型有了初步了解之后,咱们就可以简单的介绍一下使用Zookeeper如何来实现分布式锁。
Zookeeper 分布式锁应用了其临时顺序节点的特性。具体如何实现呢?让我们来看一看详细步骤:
- 获取锁
首先在Zookeeper中创建一个持久节点ParentLock,当第一个客户端要获取锁时,在ParentLock节点下创建一个临时顺序节点Lock1;
接下来客户端1会获取ParentLock下的所有临时顺序子节点并进行排序,然后与自身创建的Lock1比较,判断Lock1是不是最小的(最靠前的),如果是最靠前的,则获取锁成功;
此时,假设又有一个客户端2前来获取锁,则在ParentLock下创建一个临时顺序节点Lock2;
接下来客户端2会获取ParentLock下的所有临时顺序子节点并进行排序,然后与自身创建的Lock2比较,判断Lock2是不是最小的(最靠前的),结果发现Lock2不是最小的;于是,Lock2向排序比它靠前的第一个节点Lock1注册一个Watcher,用于监听Lock1是否存在,此时意味着客户端2抢锁失败,客户端2进入等待状态;
此时,假设现在又有一个客户端3前来获取锁,则在ParentLock下创建一个临时顺序节点Lock3;
接下来客户端3将获取ParentLock下的所有临时顺序节点并排序,与自身创建的节点Lock3比较,判断Lock3是不是最小的(最靠前的),结果发现Lock3不是最小的;于是,Lock3向比它靠前的第一个节点Lock2注册一个Watcher,用于监听Lock2是否存在,此时意味着Lock3也抢锁失败,进入阻塞状态;
这样的话,客户端1获取到了锁,客户端2监听了Lock1、客户端3监听了Lock2;
- 客户端崩溃释放锁(避免死锁)
假设由于网络原因或者其他物理原因,导致客户端1与Zookeeper失去连接,根据临时节点的特性,Zookeeper会自动删除相关联的节点Lock1。
Lock1删除之后,因为客户端2在Lock1上注册了Watcher,因此Lock1删除之后,客户端2会立即收到通知,这时客户端2会再次获取ParentLock下的所有临时顺序子节点并进行排序,然后与自身创建的Lock2比较,判断Lock2是不是最小的(最靠前的),结果发现Lock2是最小的,因此客户端2获取锁成功;
客户端2崩溃同理;
- 显示释放(客户端任务执行完毕,主动释放锁)
当客户端2执行完任务之后,调用delete方法删除Lock2节点,因为客户端3在Lock2上注册了Watcher,因此Lock2删除之后,客户端3会立即收到通知,这时客户端3会再次获取ParentLock下的所有临时顺序子节点并进行排序,然后与自身创建的Lock3比较,判断Lock3是不是最小的(最靠前的),结果发现Lock3是最小的,因此客户端3获取锁成功;
4、小结
本文简单介绍了Zookeeper的节点类型,分布式锁的概念及应用场景,使用Zookeeper实现分布式锁的简单原理,望对帮助理解Zookeeper实现分布式锁有些许帮助。