摘要:本篇博文是“Java秒杀系统实战系列文章”的第十六篇,本文我们将继续秒杀系统的优化之路,采用统一协调调度中心中间件ZooKeeper控制秒杀系统中高并发多线程对于共享资源~代码块的并发访问所出现的并发安全问题,即用ZooKeeper实现一种分布式锁!
内容:ZooKeeper,看到其名字,不由得联想至 Zoo + Keeper,即动物园的看管所!这个寓意用以表达的是一种统一协调管理思想,动物园有很多动物,这些动物就类似于分布式系统架构时代所部署的不同系统服务节点,而这些动物~服务节点偶尔可能需要打交道,相互之间可能需要进行相应的问候,这个时候得需要有一个“看管者”,其职责除了需要管理动物园里的这些动物的行为之外(即这些系统服务的行为),还需要统一协调管理这些动物之间的“问候”、“打交道”(系统服务之间的调用)!
ZooKeeper对外会提供一个多层级的节点命名空间(节点称为ZNode),每个节点都用一个以斜杠(/)分隔的路径表示,而且每个节点都有父节点(根节点除外)。ZooKeeper的相关功能特性在实际使用过程中,其底层可能需要动态的添加、删减相应的节点,此时zk会提供一个Watcher监听器,用以监听那些动态新增、删减的节点,即ZooKeeper会在某些业务场景对一些节点使用上Watcher机制,监听相应的节点的动态。
我们即将要在下面介绍的“分布式锁”功能组件即为ZooKeeper提供给开发者的一大利器,其底层的实现原理正是基于Watcher机制 + 动态创建、删减临时顺序节点 所实现的,值得一提的是,一个ZNode节点将代表一个路径。
以下为ZooKeeper实现(获取)分布式锁的原理:
(1)当前线程在获取分布式锁的时候,会在ZNode节点(ZNode节点是Zookeeper的指定节点)下创建临时顺序节点,释放锁的时候将删除该临时节点。
(2)客户端/服务 调用createNode方法在 ZNode节点 下创建临时顺序节点,然后调用getChildren(“ZNode”)来获取ZNode下面的所有子节点,注意此时不用设置任何Watcher。
(3)客户端/服务获取到所有的子节点path之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁,即当前线程获取到了分布式锁。
(4)如果发现自己创建的节点并非ZNode所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时对其注册事件监听器。
之后,让这个被关注的节点删除(核心业务逻辑执行完,释放锁的时候,就是删除该节点),则客户端的Watcher会收到相应的通知,此时再次判断自己创建的节点是否是ZNode子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。
以上为ZooKeeper的基本介绍以及关于其底层实现分布式锁的原理的介绍,但是,Debug想说的是“理论再好,如果不会转化为实际的代码或者输出,那只能称之为泛泛而谈、吹牛逼” !
下面,我们将基于Spring Boot搭建的秒杀系统整合ZooKeeper,并基于ZooKeeper实现一种分布式锁,以此解决秒杀系统中高并发多线程并发产生的诸多问题。
(1)首先,当然是引入ZooKeeper的依赖啦,其中zk的版本在这里我们采用3.4.10,zk客户端操作实例curator的版本为2.12.0
<!-- zookeeper start -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
紧接着,是在配置文件application.properties中加入ZooKeeper的配置,包括其服务所在的Host、端口Port、命名空间等等:
#zookeeper
zk.host=127.0.0.1:2181
zk.namespace=kill