zookeeper官网地址:
http://zookeeper.apache.org/
zookeeper也被称为分布式环境指挥官、分布式系统协调服务,是分布式系统中很常见的一个基础组件。本文不对zookeeper做过多赘述,只简单介绍一下zk及其相关特性,以便于理解使用zk做分布式锁的便利性。
文章目录
一、什么是zookeeper
Apache ZooKeeper是一种用于分布式系统的高性能协调服务,提供一种集中式信息存储服务。
特点:数据存储于内存中,类似于文件系统中的属性结构(文件和目录),高吞吐量和低延迟,集群可用性高
结构:zookeeper是C/S结构、即Client-Server
应用:基于zookeeper可以实现分布式统一配置中心、服务注册中心、分布式锁、分布式队列、命名服务、Master选举…等等功能。
小结:
zk 实质上就是一个高可用、高性能的小量数据存取服务,结合它的特性(临时顺序节点、watch机制、全局有序事务...),特别适合做分布式系统协调服务。
二、zookeeper核心概念
1. Session - 会话
- zk客户端与zk服务端连接之后,产生一个会话;
- 一个客户端连接一个会话,由zk分配唯一会话ID;
- 客户端以特定时间间隔发送心跳以保持会话有效;
- 超过会话时间未收到客户端心跳,则判定客户端死了;
- 会话中请求按FIFO(先进先出)顺序执行。
2. 数据模型
以 / 为根节点
节点可以包含数据以及子节点(既是文件也是文件夹)
节点的路径总是表示为规范的、绝对的、斜杠(/)分隔的路径
znode:
zookeeper中,每个节点也被被称为一个znode;特点:名称唯一
,命名规范
2.1 znode - 节点类型
持久节点:不delete就一直在zk中存在
##PERSISTENT
[zk: localhost:2181(CONNECTED) 1] create /jin/y1 ""
Created /jin/y1
临时节点:客户端与zk服务端会话结束后就会被删除
##EPHEMERAL
[zk: localhost:2181(CONNECTED) 3] create -e /jin/y3 ""
Created /jin/y3
顺序节点:zookeeper给该节点名称进行顺序编号
- 十位十进制序号
- 每个父节点有一个计数器
- 计数器是带符号int(4字节),到2147483647之后将溢出(导致名称“
-2147483648”)
##PERSISTENT_SEQUENTIAL
[zk: localhost:2181(CONNECTED) 2] create -s /jin/y2 ""
Created /jin/y20000000001
临时顺序节点:会话结束就被删除的顺序节点
##EPHEMERAL_SEQUENTIAL
[zk: localhost:2181(CONNECTED) 4] create -s -e /jin/y4 ""
Created /jin/y40000000001
2.2 watch监听机制
监听如:节点的变化、节点是否存在、子节点的变化...
客户端可以在znode上设置watch, 监听znode的变化;znode发生变化时通知客户端。
注意:
1)因为watch是一次性触发器,获取时间和设置下一个watch有延迟,所以对监听没有强可靠性
2)一个watch对象只会被特定的通知触发一次
三、zookeeper实现分布式锁原理
方式一:znode节点命名唯一 + watch机制
原理:znode节点命名唯一 + watch机制
- 争抢创建同名的临时节点
- 创建成功即抢到锁,执行业务完业务代码,删掉节点
- 创建不成功就创建watcher监控锁,阻塞等待
- watch监测到锁节点被删除,取消watcher继续争抢锁,重复上述步骤
为何是用临时节点?
防止获得锁的节点出现问题,down掉之后未释放锁
缺点:惊群效应 (并发量大的时候,反复唤醒大量线程,占用系统资源,会造成很大的网络冲击,甚至可能会让zk服务挂掉)
总结:实现简单,适用于并发量小的情况。
方式二:临时顺序节点 + watch机制
原理:临时顺序节点 + watch机制
- 需要争抢锁的线程,在同一路径下依次创建顺序节点
- 创建完成后,获得所有子节点,判断自己是否是当前最小号
- 是最小号获得锁
- 不是最小号,对前一个节点注册watcher,阻塞等待
- 获得锁的节点释放锁后重复上述步骤
总结:避免了方式一的惊群效应问题,适合高并发场景。
四、Java代码实现
这里对方式二的分布式锁,做一个简单的实现;
使用的API是zkClient:
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
相关变量定义:
public class ZKDistributedLock implements Lock {
// 锁路径(临时顺序节点们的父路径)
private String lockPath;
// zk客户端
private ZkClient client;
// 这里为了方便,用ThreadLocal实现的线程安全,也可以用其他方式
// 当前节点的路径
private ThreadLocal<String> currentPath = new ThreadLocal<>()