【中间件 | 分布式 | Java】zookeeper实现分布式锁原理及代码实现

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机制

  1. 争抢创建同名的临时节点
  2. 创建成功即抢到锁,执行业务完业务代码,删掉节点
  3. 创建不成功就创建watcher监控锁,阻塞等待
  4. watch监测到锁节点被删除,取消watcher继续争抢锁,重复上述步骤

为何是用临时节点?
防止获得锁的节点出现问题,down掉之后未释放锁

缺点:惊群效应 (并发量大的时候,反复唤醒大量线程,占用系统资源,会造成很大的网络冲击,甚至可能会让zk服务挂掉)
总结:实现简单,适用于并发量小的情况。

方式二:临时顺序节点 + watch机制

原理:临时顺序节点 + watch机制

  1. 需要争抢锁的线程,在同一路径下依次创建顺序节点
  2. 创建完成后,获得所有子节点,判断自己是否是当前最小号
  3. 是最小号获得锁
  4. 不是最小号,对前一个节点注册watcher,阻塞等待
  5. 获得锁的节点释放锁后重复上述步骤

总结:避免了方式一的惊群效应问题,适合高并发场景。

四、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<>()
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值