Zookeeper 及 基于ZooKeeper实现的分布式锁
1 ZooKeeper
1.1 ZooKeeper 介绍
ZooKeeper是一个开源的分布式协调服务,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
原语:操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。
ZooKeeper为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。这些功能的实现主要依赖于ZooKeeper提供的 数据存储+事件监听 功能。
ZooKeeper将数据保存在内存中,性能是不错的。在“读”多于写的应用程序中尤其的高性能,因为"写"会导致所有的服务器间同步状态。(“读”多于"写"实现协调服务的典型场景)。
另外,很多顶级的开源项目都用到了ZooKeeper,比如:
- Kafka : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。不过,在 Kafka 2.8 之后,引入了基于 Raft 协议的 KRaft 模式,不再依赖 Zookeeper,大大简化了 Kafka 的架构。
- Hbase : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
- Hadoop : ZooKeeper 为 Namenode 提供高可用支持。
1.2 ZooKeeper 特点
1.2.1 顺序一致性
从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到ZooKeeper上去。
1.2.2 原子性
所有事物请求的处理结果在整个集群中的所有机器上的应用情况是一致的,也就是说,要么整个集群中的所有的机器都成功应用了某一个事务,要么都没有应用。
1.2.3 单一系统映像
无论客户端连到哪一个ZooKeeper服务器上,其看到的服务器端数据模型都是一致的。
1.2.4 可靠性
一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次覆盖。
1.2.5 实时性
一旦数据发生变更,其他节点会实时感知到。每个客户端的系统视图都是最新的。
1.2.6 集群部署
3~5台(最好奇数台)机器就可以组成一个集群,每台机器都在内存保存了ZooKeeper的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以。
1.2.7 高可用
如果某台机器宕机,会保证数据不丢失。集群中挂掉不超过一半的机器,都能保证集群可用。比如3台机器可以挂一台,5台可以挂两台。
1.3 ZooKeeper 应用场景
ZooKeeper介绍中,我们知道了使用ZooKeeper来实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。
下面有三个典型的应用场景:
- 命名服务:可以通过ZooKeeper的顺序节点生成全局唯一ID
- 数据发布/订阅:通过 Watcher 机制(下文有介绍)可以很方便的实现数据订阅/发布。当你将数据发布到ZooKeeper被监听的节点上,其他机器可以通过监听ZooKeeper上的节点变化来实现配置的动态更新。
- 分布式锁:通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。分布式锁的实现也需要用到 Watcher机制。
实际上,这些功能的实现基本都得益于ZooKeeper可以保存数据的功能,但是ZooKeeper不适合保存大量数据,这一点需要注意。
1.4 ZooKeeper 重点
1.4.1 Data model(数据模型)
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字
字符串或者是二进制序列。并且,每个节点还可以拥有N个子结点,最上层是根结点以"/"来代表。每个数据节点在ZooKeeper中被称为 znode ,它是ZooKeeper中数据的最小单元。并且,每个znode都有一个唯一的路径标识。
ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在znode上,ZooKeeper给出的每个节点的数据大小上限是1M
如下图,ZooKeeper 节点路径标识方式和Unix文件系统路径非常相似,都是由一系列使用"/"进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子结点。
1.4.2 znode (数据节点)
介绍了 ZooKeeper 树形数据结构模型之后,我们知道每个数据节点在ZooKeeper中被称为znode,它是ZooKeeper中数据的最小单元。要存放的数据就在上面。
通常将znode分为4大类:
- 持久(PERSISTENT)节点 :一旦创建就一直存在即使 Zookeeper 集群宕机直到将其删除。
- 临时(EPHEMERAL)节点:临时节点的生命周期是与客户端会话绑定的,会话消失则节点消失,并且,临时节点只能做叶子结点,不能创建子节点。
- 持久顺序(PERSISTENT_SEQUENTIAL)节点:除了具有持久节点的特性之外,子节点的名称还具有顺序性。比如:/node1/app0000000001、/node1/app0000000002
- 临时顺序(EPHEMERAL_SEQUENTIAL)节点:除了具备临时节点的特性之外,子节点的名称还具有顺序性。
每个znode由2部分组成:
- stat:状态信息
- data:节点存放的数据的具体内容。
如下图所示,我通过get命令来获取根目录下的dubbo节点的内容。(get命令在下面会介绍到)。
[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
# 该数据节点关联的数据内容为空
null
# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
cZxid = 0x2
ctime = Tue Nov 27 11:05:34 CST 2018
mZxid = 0x2
mtime = Tue Nov 27 11:05:34 CST 2018
pZxid = 0x3
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
Stat类中包含了一个数据节点的所有状态信息的字段,包括事务ID、节点创建时间和子节点个数等等。
下图所示为每个znode状态信息所代表的东西:
1.4.3 版本
在前面我们会提到,对应于每个znode,ZooKeeper都会为其维护一个叫做 Stat 的数据结构,Stat中记录了这个znode的三个相关版本:
- dataVersion:当前 znode 节点的版本号
- cversion:当前 znode 子节点的版本
- aclVersion:当前 znode 的 ACL 的版本。
1.4.4 ACL (权限控制)
ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于UNIX文件系统的权限控制。
对于znode操作的权限,ZooKeeper提供了以下5种:
- CREATE : 能创建子节点
- READ:能获取节点数据和列出其子节点
- WRITE : 能设置/更新节点数据
- DELETE : 能删除子节点
- ADMIN : 能设置节点 ACL 的权限
其实尤其需要注意的是 CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。
对于身份验证,提供了一下几种方式:
- world:默认方式,所有用户都可无条件访问。
- auth :不使用任何 id,代表任何已认证的用户。
- digest :用户名:密码认证方式:username:password 。
- ip : 对指定 ip 进行限制。
1.4.4 Watcher (事件监听器)
Watcher (事件监听器) ,是ZooKeeper中的一个很重要的特性。ZooKeeper允许用户在指定节点上注册一些Watcher (事件监听器) ,并且在一些特定事件触发的时候ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是ZooKeeper实现分布式协调服务的重要特性。
1.4.5 会话(Session)
Session 可以看做是ZooKeeper服务器与客户端的之间的一个TCP长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的对话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能够通过该连接接受来自服务器的Watcher事件通知。
Session有一个属性叫做:sessionTimeout,sessionTimeout代表会画的超时时间。当由于服务器压力太大,网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。
另外,在为客户端创建会话之前,服务器端首先为每个客户端都分配一个sessionID。由于sessionID是ZooKeeper会话的一个重要标识,许多会话相关的运行机制都是基于这个sessionID的,因此,无论是哪台服务器为客户端分配的sessionID,都务必保证全局唯一。
1.5 ZooKeeper集群
为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性。
最典型集群模式:Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
1.6 ZooKeeper 集群角色
但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示
ZooKeeper 集群中的所有机器通过一个 Leader 选举过程 来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
1.7 ZooKeeper集群Leader 选举过程
当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。
这个过程大致是这样的:
- Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
- Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
- Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准 leader 才会成为真正的 leader。
- Broadcast(广播阶段):到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
ZooKeeper 集群中的服务器状态有下面几种:
- LOOKING:寻找 Leader。
- LEADING:Leader 状态,对应的节点为 Leader。
- FOLLOWING:Follower 状态,对应的节点为 Follower。
- OBSERVING:Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
1.8 ZooKeeper 集群为啥最好为奇数台?
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。
假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。
综上,何必增加那一个不必要的 ZooKeeper 呢?
1.9 ZooKeeper选举的过半机制防止脑裂
为集群脑裂?
对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况。
举例说明:比如现在有一个由 6 台服务器所组成的一个集群,部署在了 2 个机房,每个机房 3 台。正常情况下只有 1 个 leader,但是当两个机房中间网络断开的时候,每个机房的 3 台服务器都会认为另一个机房的 3 台服务器下线,而选出自己的 leader 并对外提供服务。若没有过半机制,当网络恢复的时候会发现有 2 个 leader。仿佛是 1 个大脑(leader)分散成了 2 个大脑,这就发生了脑裂现象。脑裂期间 2 个大脑都可能对外提供了服务,这将会带来数据一致性等问题。
过半机制是如何防止脑裂现象产生的?
ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂。
1.10 ZAB 协议和Paxo算法
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在 ZooKeeper 的官方文档中也指出,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法。
ZAB:
ZAB(ZooKeeper Atomic Broadcast,原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB协议两中基本的模式:崩溃恢复和消息广播:
ZAB 协议包括两种基本的模式,分别是
- 崩溃恢复:当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。
- 消息广播:当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
1.11 ZooKeeper总结
- ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
- 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。
- ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 znode 中存储的数据量较小的进一步原因)。
- ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地明显,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
- ZooKeeper 有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个 znode 被创建了,除非主动进行 znode 的移除操作,否则这个 znode 将一直保存在 ZooKeeper 上。
- ZooKeeper 底层其实只提供了两个功能:① 管理(存储、读取)用户程序提交的数据;② 为用户程序提供数据节点监听服务。
2 基于ZooKeeper实现分布式锁
ZooKeeper 相比于 Redis 实现分布式锁,除了提供相对更高的可靠性之外,在功能层面还有一个非常有用的特性:Watch 机制。这个机制可以用来实现公平的分布式锁。不过,使用 ZooKeeper 实现的分布式锁在性能方面相对较差,因此如果对性能要求比较高的话,ZooKeeper 可能就不太适合了。
2.1 如何基于ZooKeeper实现分布式锁?
ZooKeeper 分布式锁是基于 临时顺序节点 和 Watcher(事件监听器) 实现的(上文提及过)。
获取锁:
- 首先我们要有一个持久节点
/locks
,客户端获取锁就是在locks
下创建临时顺序节点。 - 假设客户端 1 创建了
/locks/lock1
节点,创建成功之后,会判断lock1
是否是/locks
下最小的子节点。 - 如果
lock1
是最小的子节点,则获取锁成功。否则,获取锁失败。 - 如果获取锁失败,则说明有其他的客户端已经成功获取锁。客户端 1 并不会不停地循环去尝试加锁,而是在前一个节点比如
/locks/lock0
上注册一个事件监听器。这个监听器的作用是当前一个节点释放锁之后通知客户端 1(避免无效自旋),这样客户端 1 就加锁成功了。
释放锁:
- 成功获取锁的客户端在执行完业务流程之后,会将对应的子节点删除。
- 成功获取锁的客户端在出现故障之后,对应的子节点由于是临时顺序节点,也会被自动删除,避免了锁无法被释放。
- 我们前面说的事件监听器其实监听的就是这个子节点删除事件,子节点删除就意味着锁被释放。
实际项目中,推荐使用 Curator 来实现 ZooKeeper 分布式锁。Curator 是 Netflix 公司开源的一套 ZooKeeper Java 客户端框架,相比于 ZooKeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
Curator
主要实现了下面四种锁:
InterProcessMutex
:分布式可重入排它锁InterProcessSemaphoreMutex
:分布式不可重入排它锁InterProcessReadWriteLock
:分布式读写锁InterProcessMultiLock
:将多个锁作为单个实体管理的容器,获取锁的时候获取所有锁,释放锁也会释放所有锁资源(忽略释放失败的锁)。
CuratorFramework client = ZKUtils.getClient();
client.start();
// 分布式可重入排它锁
InterProcessLock lock1 = new InterProcessMutex(client, lockPath1);
// 分布式不可重入排它锁
InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2);
// 将多个锁作为一个整体
InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));if (!lock.acquire(10, TimeUnit.SECONDS)) {throw new IllegalStateException("不能获取多锁");
}
System.out.println("已获取多锁");
System.out.println("是否有第一个锁: " + lock1.isAcquiredInThisProcess());
System.out.println("是否有第二个锁: " + lock2.isAcquiredInThisProcess());
try {// 资源操作resource.use();
} finally {System.out.println("释放多个锁");lock.release();
}
System.out.println("是否有第一个锁: " + lock1.isAcquiredInThisProcess());
System.out.println("是否有第二个锁: " + lock2.isAcquiredInThisProcess());
client.close();
2.2 为什么要用临时顺序节点?
每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。
我们通常是将 znode 分为 4 大类:
- 持久(PERSISTENT)节点:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
- 临时(EPHEMERAL)节点:临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失 。并且,临时节点只能做叶子节点 ,不能创建子节点。
- 持久顺序(PERSISTENT_SEQUENTIAL)节点:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如
/node1/app0000000001
、/node1/app0000000002
。 - 临时顺序(EPHEMERAL_SEQUENTIAL)节点:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
可以看出,临时节点相比持久节点,最主要的是对会话失效的情况处理不一样,临时节点会话消失则对应的节点消失。这样的话,如果客户端发生异常导致没来得及释放锁也没关系,会话失效节点自动被删除,不会发生死锁的问题。
使用 Redis 实现分布式锁的时候,我们是通过过期时间来避免锁无法被释放导致死锁问题的,而 ZooKeeper 直接利用临时节点的特性即可。
假设不使用顺序节点的话,所有尝试获取锁的客户端都会对持有锁的子节点加监听器。当该锁被释放之后,势必会造成所有尝试获取锁的客户端来争夺锁,这样对性能不友好。使用顺序节点之后,只需要监听前一个节点就好了,对性能更友好。
2.3 为什么要设置对前一个节点的监听?
Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
同一时间段内,可能会有很多客户端同时获取锁,但只有一个可以获取成功。如果获取锁失败,则说明有其他的客户端已经成功获取锁。获取锁失败的客户端并不会不停地循环去尝试加锁,而是在前一个节点注册一个事件监听器。
这个事件监听器的作用是:当前一个节点对应的客户端释放锁之后(也就是前一个节点被删除之后,监听的是删除事件),通知获取锁失败的客户端(唤醒等待的线程,Java 中的 wait/notifyAll
),让它尝试去获取锁,然后就成功获取锁了。
2.4 如何实现可重用锁?
这里以 Curator 的 InterProcessMutex
对可重入锁的实现来介绍(源码地址:InterProcessMutex.java)。
当我们调用 InterProcessMutex#acquire
方法获取锁的时候,会调用InterProcessMutex#internalLock
方法。
// 获取可重入互斥锁,直到获取成功为止
@Override
public void acquire() throws Exception {if (!internalLock(-1, null)) {throw new IOException("Lost connection while trying to acquire lock: " + basePath);}
}
internalLock
方法会先获取当前请求锁的线程,然后从 threadData
( ConcurrentMap<Thread, LockData>
类型)中获取当前线程对应的 lockData
。 lockData
包含锁的信息和加锁的次数,是实现可重入锁的关键。
第一次获取锁的时候,lockData
为 null
。获取锁成功之后,会将当前线程和对应的 lockData
放到 threadData
中
private boolean internalLock(long time, TimeUnit unit) throws Exception {// 获取当前请求锁的线程Thread currentThread = Thread.currentThread();// 拿对应的 lockDataLockData lockData = threadData.get(currentThread);// 第一次获取锁的话,lockData 为 nullif (lockData != null) {// 当前线程获取过一次锁之后// 因为当前线程的锁存在, lockCount 自增后返回,实现锁重入.lockData.lockCount.incrementAndGet();return true;}// 尝试获取锁String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());if (lockPath != null) {LockData newLockData = new LockData(currentThread, lockPath);// 获取锁成功之后,将当前线程和对应的 lockData 放到 threadData 中threadData.put(currentThread, newLockData);return true;}return false;
}
LockData
是 InterProcessMutex
中的一个静态内部类。
private final ConcurrentMap<Thread, LockData> threadData = Maps.newConcurrentMap();private static class LockData
{// 当前持有锁的线程final Thread owningThread;// 锁对应的子节点final String lockPath;// 加锁的次数final AtomicInteger lockCount = new AtomicInteger(1);private LockData(Thread owningThread, String lockPath){this.owningThread = owningThread;this.lockPath = lockPath;}
}
如果已经获取过一次锁,后面再来获取锁的话,直接就会在 if (lockData != null)
这里被拦下了,然后就会执行lockData.lockCount.incrementAndGet();
将加锁次数加 1。
整个可重入锁的实现逻辑非常简单,直接在客户端判断当前线程有没有获取锁,有的话直接将加锁次数加 1 就可以了。
参考:
《分布式锁常见实现方案总结》
《ZooKeeper相关概念总结》
相关文章:
Zookeeper 及 基于ZooKeeper实现的分布式锁
1 ZooKeeper 1.1 ZooKeeper 介绍 ZooKeeper是一个开源的分布式协调服务,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。 原语:操作系统或…...
内嵌踢脚线怎么做能省钱?
家里装修内嵌踢脚线应该怎么做?哪种做法更经济? 先回答内嵌踢脚线应该怎么做? 做内嵌踢脚线有两种办法,简单点来说就是前凸和后挖。 前凸是踢脚线安到原来墙面上,踢脚线是从墙面上凸出来的,想要实现内嵌的效…...
DeepSeek集成到VScode工具,让编程更高效
DeepSeek与VScode的强强联合,为编程效率树立了新标杆。 DeepSeek,一款卓越的代码搜索引擎,以其精准的索引和高速的检索能力,助力开发者在浩瀚的代码海洋中迅速定位关键信息。 集成至VScode后,开发者无需离开熟悉的编辑…...
大模型应用:多轮对话(prompt工程)
概述 在与大型语言模型(如ChatGPT)交互的过程中,我们常常体验到与智能助手进行连贯多轮对话的便利性。那么,当我们开启一个新的聊天时,系统是如何管理聊天上下文的呢? 一、初始上下文的建立 1. 创建新会…...
洛谷 P1601 A+B Problem(高精)详解c++
我们之前做题碰到的数据范围一般是10^9,多点会达到10^18级别,处理10^9用int就可以存下,10^18次方要用到long long,接着解决加减乘除的问题,但是当数据范围达到了10^10^6的时候,当数据的值特别⼤,…...
【云原生】Spring Cloud是什么?Spring Cloud版本介绍
什么是SpringCloud 上一章节介绍了总体的SpringCloud的总体学习章节,因为最近项目刚好需要用到SpringCloud来搭建微服务项目、所以就跟着大家一起来再学习巩固下SpringCloud的相关知识 SpringCloud是基于SpringBoot提供了一套微服务解决方案,包括服务注…...
最节省成本的架构方案:无服务器架构
无服务器架构(Serverless Architecture)是一种颠覆性的云计算执行模型,它允许开发者专注于编写和部署代码,而无需担心底层服务器基础设施的管理。这种架构以其按需付费、自动伸缩和简化部署等优势,在成本优化方面表现出…...
C++入门续集:
1. 缺省参数: 我们看我们的上图,我们可以看我们的函数Func,我们可以看到我们的函数里面的参数写的是int a 0;这个写法是我们没有见过的,我们之前在C语言里面只见到过说是函数里面会设置参数,但是参数是没有…...
线代[9]|线性代数主要内容及其发展简史(任广千《线性代数的几何意义》的附录1)
文章目录 向量行列式矩阵线性方程组二次型 向量 向量又称为矢量,最初应用与物理学。很多物理量如力、速度、位移以及电场强度、磁感应强度等等都是向量。大约公元前350年前,古希腊著名学者亚里士多德就知道了力可以表示成向量,两个力的组合作…...
C++ Primer 动态内存与智能指针
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
AI在原型设计中的革命性应用:效率与创新的双重突破
引言 在数字化转型加速的今天,产品开发周期被压缩至前所未有的程度。原型设计作为产品开发的核心环节,其效率和质量直接影响最终产品的市场竞争力。传统原型设计流程依赖人工绘制、反复修改和用户测试,耗时且成本高昂。而人工智能࿰…...
网络学习(四)HTTPS中,SSL的单向认证与双向认证
目录 一、什么是SSL?1.1 SSL 的主要功能1.2 SSL 的工作原理1.3 SSL 的核心组件1.4 SSL 的应用场景1.5 SSL 与 TLS 的区别 二、SSL 单向认证、双向认证2.1 SSL 单向认证2.2 SSL 双向认证2.3 总结:SSL 单向认证和双向认证的区别 一、什么是SSL?…...
分类任务和回归任务的区别
分类任务和回归任务是机器学习中两种常见的监督学习任务,尽管它们都属于预测性的分析,但它们的目标和应用场景存在显著的差异。 分类任务 定义:分类任务是指将输入样本映射到一个有限且离散的类别集合中的过程。目标是对数据进行分类&#…...
android接入rocketmq
一 前言 RocketMQ 作为一个功能强大的消息队列系统,不仅支持基本的消息发布与订阅,还提供了顺序消息、延时消息、事务消息等高级功能,适应了复杂的分布式系统需求。其高可用性架构、多副本机制、完善的运维管理工具,以及安全控制…...
V4L2框架基础
一、V4L2视频设备驱动基础 1.V4L2是专门为Linux设备设计的整合视频框架(其主要核心在Linux内核,相当于Linux操作系统上层的视频源捕获驱动框架)。为上层访问系统底层的视频设备提供一个统一的标准接口。V4L2驱动框架能够支持多种类型&#x…...
【微知】如何通过mlxlink查看Mellanox网卡和光模块相关的信息?( mlxlink -d 01:00.0 -m)
背景 通过mlxlink可以查看Mellanox网卡的一些链路信息和硬件信息,也可以查看所插入的光模块的一些信息。 兄弟篇通过ethtool查看的方法:如何查看Mellanox网卡上的光模块的信息? 命令 mlxlink -d 01:00.0 -mman手册介绍: 如果…...
使用pytorch和opencv根据颜色相似性提取图像
需求:将下图中的花朵提取出来。 代码: import cv2 import torch import numpy as np import timedef get_similar_colors(image, color_list, threshold):# 将图像和颜色列表转换为torch张量device torch.device(cuda if torch.cuda.is_available() el…...
HTML label 标签使用
点击 <label> 标签通常会使与之关联的表单控件获得焦点或被激活。 通过正确使用 <label> 标签,可以使表单更加友好和易于使用,同时提高整体的可访问性。 基本用法 <label> 标签通过 for 属性与 id 为 username 的 <input> 元素…...
SQL注入的分类靶场实践
前言 SQL 注入(SQL Injection)是一种常见且危险的 Web 安全漏洞,攻击者通过在输入字段中插入恶意 SQL 代码,能够绕过应用程序的验证机制,直接操纵数据库。本文将介绍 SQL 注入的分类,并通过 Pikachu 靶场进…...
用matplotlib构建BI看板:Superset插件开发实战
目录 前言:当经典可视化库遇见BI航母 一、Superset插件架构精要 1.1 核心模块解析 1.2 插件通信机制 二、开发环境准备 2.1 依赖矩阵 三、开发自定义可视化插件 3.1 插件脚手架 3.2 渲染引擎适配 四、Superset深度集成 4.1 控制面板配置 4.2 动态参数传递…...
【Linux】之【Bug】VMware 虚拟机开机 一直卡在黑屏左上角下划线闪烁界面
解决 参考: 解决Ubuntu20.04 开机黑屏光标闪烁进不去系统 Centos根目录100%解决思路 当前界面 ctrlaltf3-f6 暂时进入终端界面 df -h 查看发现根目录 磁盘空间已满 执行命令 查看当前目录占用内存明细 sudo du -h -x --max-depth1清理无用的大内存文件 或者安装…...
【练习】【链表】力扣热题100 19. 删除链表的倒数第 N 个结点
题目 删除链表的倒数第 N 个结点 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例 1: 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5] 示例 2: 输入:head [1], n 1 输出&…...
SQL-labs13-16闯关记录
http://127.0.0.1/sqli-labs/less-13/ 基于POST单引号双注入变形 1,依然是一个登录框,POST型SQL注入 2,挂上burpsuite,然后抓取请求,构造请求判断漏洞类型和闭合条件 admin 发生了报错,根据提示闭合方式是(…...
【Linux】进程信号——信号保存和信号捕捉
文章目录 信号保存信号相关的概念信号是如何保存的呢?有关信号保存的系统调用sigprocmask信号的增删查改查看pending表验证接口 信号捕捉用户态与内核态信号捕捉流程 总结 信号保存 信号相关的概念 信号递达:指 操作系统 将一个信号(Signal…...
任务9:交换机基础及配置
CSDN 原创主页:不羁https://blog.csdn.net/2303_76492156?typeblog 一、交换机基础 交换机的概念:交换机是一种网络设备,用于连接多台计算机或网络设备,实现数据包在局域网内的快速交换。交换机基于MAC地址来转发数据包&#x…...
【DFS/回溯算法】2016年蓝桥杯真题之路径之谜详解
目录: 1.题目描述: 输入格式 输出格式 2.题解: 3.详细c代码 1.题目描述: 小明冒充 X 星球的骑士,进入了一个奇怪的城堡。 城堡里边什么都没有,只有方形石头铺成的地面。 假设城堡地面是 nn 个方格。…...
深度学习R8周:RNN实现阿尔兹海默症(pytorch)
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 数据集包含2149名患者的广泛健康信息,每名患者的ID范围从4751到6900不等。该数据集包括人口统计详细信息、生活方式因素、病史、临床测量、认知和功…...
字节跳动发布 Trae AI IDE!支持 DeepSeek R1 V3,AI 编程新时代来了!
3 月 3 日,字节跳动重磅发布国内首款 AI 原生集成开发环境(AI IDE)——Trae 国内版! Trae 不只是一个传统的 IDE,它深度融合 AI,搭载 doubao-1.5-pro 大模型,同时支持DeepSeek R1 & V3&…...
智能合约安全指南 [特殊字符]️
智能合约安全指南 🛡️ 1. 安全基础 1.1 常见漏洞类型 重入攻击整数溢出权限控制缺陷随机数漏洞前后运行攻击签名重放 1.2 安全开发原则 最小权限原则检查-生效-交互模式状态机安全失败保护机制 2. 重入攻击防护 2.1 基本防护模式 contract ReentrancyGuarde…...
DeepSeek 全套资料pdf合集免费下载(持续更新)
有很多朋友都关注DeepSeek相关使用的教程资料,本站也一直持续分享DeepSeek 学习相关的pdf资料,由于比较零散,这篇文章主要就是做一个汇总,并且持续更新,让大家可以及时获取下载最新的相关DeepSeek的资料。 持续更新地…...
IDEA 接入 Deepseek
在本篇文章中,我们将详细介绍如何在 JetBrains IDEA 中使用 Continue 插件接入 DeepSeek,让你的 AI 编程助手更智能,提高开发效率。 一、前置准备 在开始之前,请确保你已经具备以下条件: 安装了 JetBrains IDEA&…...
将md格式转jupyter并运行
将md格式转jupyter并运行 有时候我们需要将这种文档以学习的形式记笔记到jupyter中(任务) 但是内容太多了,一个一个粘贴又不方便,怎么办呢? 发现直接粘贴到md中是带格式的!!! 那…...
SOUI基于Zint生成EAN码
EAN码广泛应用与欧洲的零售业。包括EAN-2、EAN-5、EAN-8和EAN-12码。分别编码 2、5、7 或 12 位数字。此外,可以使用 字符将 EAN-2 和 EAN-5 附加符号添加到 EAN-8 和 EAN-13 符号中,就像 UPC 符号一样。 EAN-8校验码计算: 从左往右奇数位的…...
StarRocks-fe工程在Cursor中不能识别为Java项目
SR简介 StarRocks 是一款高性能分析型数据库,支持实时、多维度、高并发的数据分析。本指南旨在解决在使用 VSCode 或 Cursor 开发 StarRocks 后端项目时遇到的模块识别问题。 问题描述 使用 Cursor 或 VSCode 打开 StarRocks 的后端工程 fe 时,spark-…...
AI是否能真正理解人类情感?从语音助手到情感机器人
引言:AI与情感的交集 在过去的几十年里,人工智能(AI)的发展速度令人惊叹,从简单的语音识别到如今的深度学习和情感计算,AI已经深入到我们生活的方方面面。尤其是在语音助手和情感机器人领域,AI不…...
【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(成功版)
【Linux】【网络】UDP打洞–>不同子网下的客户端和服务器通信(成功版) 根据上个文章的分析 问题可能出现在代码逻辑上面 我这里重新查找资料怀疑: 1 NAT映射可能需要多次数据包的发送才能建立。 2 NAT映射保存时间太短ÿ…...
【微知】如何查看Mellanox网卡上的光模块的信息?(ethtool -m enp1s0f0 看型号、厂商、生产日期等)
背景 服务器上插入的光模块经常被忽略,往往这里是定位问题最根本的地方。如何通过命令查看? 命令 ethtool提供了-m参数,m是module-info的意思,他是从光模块的eeprom中读取数据。(应该是用i2c协议读取的)…...
图论基础算法: 二分图的判定(C++)
二分图的基本概念 什么是二分图? 二分图(Bipartite Graph)是指一个图的顶点集可以被分割为两个互不相交的子集 U U U 和 V V V, 并且图中的每一条边都连接 U U U 中的一个顶点和 V V V 中的一个顶点. 换句话说, 二分图中的顶点可以被分成两组, 组内的顶点之间没有边相连…...
AI赋能校园安全:科技助力预防与应对校园霸凌
校园本应是学生快乐学习、健康成长的地方,然而,校园霸凌却成为威胁学生身心健康的隐形“毒瘤”。近年来,随着人工智能(AI)技术的快速发展,AI在校园安全领域的应用逐渐成为解决校园霸凌问题的新突破口。通过…...
PyTorch系列教程:评估和推理模式下模型预测
使用PyTorch时,将模型从训练阶段过渡到推理阶段是至关重要的一步。在推理过程中,该模型用于对以前从未见过的新数据进行预测。这种转换的一个重要方面是使用推理模式,它通过禁用仅在训练期间需要的操作来帮助优化模型的性能。 理解推理模式 …...
Linux注册进程终止处理函数
atexit() 是一个标准库函数,用于注册在进程正常终止时要调用的函数。通过 atexit(),你可以确保在程序结束时自动执行一些清理工作,比如释放资源、保存状态等。 函数原型如下: #include <stdlib.h> int atexit(void (*func…...
Lumerical INTERCONNECT 中的自相位调制 (SPM)
一、自相位调制的数学介绍 A.非线性薛定谔方程(NLSE): NLSE 是光学中的一个关键方程。它告诉我们光脉冲在具有非线性和色散特性的介质中的行为方式。该方程如下所示: i ∂A/∂z β2/2 ∂A/∂t γ|A|A 0 其中: - …...
DICOM服务中的C-STORE、 C-FIND、C-MOVE、C-GET、Worklist
DICOM服务说明 DICOM(Digital Imaging and Communications in Medicine)是一种用于处理、存储、打印和传输医学影像的标准。DICOM定义了多种服务类,其中C-STORE、C-FIND、C-MOVE和C-GET是与影像数据查询和检索相关的四个主要服务类ÿ…...
Python的pdf2image库将PDF文件转换为PNG图片
您可以使用Python的pdf2image库将PDF文件转换为PNG图片。以下是一个完整的示例,包含安装步骤、代码示例和注意事项。 安装依赖库 首先,您需要安装pdf2image库: pip install pdf2imagepdf2image依赖于poppler库来解析PDF文件。 Windows系统…...
在Blender中给SP分纹理组
在Blender中怎么分SP的纹理组/纹理集 其实纹理组就是材质 把同一组的材质分给同一组的模型 导入到sp里面自然就是同一个纹理组 把模型导入SP之后 就自动分好了...
import模块到另一个文件夹报错:ModuleNotFoundError: No module named xxx
1. 问题 打开项目文件夹my_code,将bb.py的函数或者类import到aa.py中,然后运行aa.py文件,可能会报错ModuleNotFoundError: No module named xxx。 E:\Desktop\my_code ├── a │ ├── train.sh │ └── aa.py └── b└── b…...
[SystemVerilog]例化
SystemVerilog 的例化方式和Verilog 类似 如果信号输入输出name一致 abc abc_inst( .a(a), .b(b), c(c) ); 使用SystemVerilog abc abc_inst( .a, .b, .c ); 或者 abc abc_inst( .* ); 在SystemVerilog中,可以简化例化方式。 可以使用…...
Java方法详解
Java方法详解 方法1.方法的概念(1).什么是方法(2).方法的定义(3).实参与形参的关系 2.方法重载(1).方法重载的概念 3.递归(C语言详细讲过) 方法 1.方法的概念 (1).什么是方法 方法类似于C语言中的函数,我们重在体会与理解,不必…...
springboot自动插入创建时间和更新时间到数据库
springboot自动插入创建时间和更新时间到数据库 1.添加TableField注解2.添加TimeMetaObjectHandler配置3.测试 1.添加TableField注解 /*** 创建时间*/TableField(fill FieldFill.INSERT) // 插入时生效private LocalDateTime createTime;/*** 修改时间*/TableField(fill Fiel…...
如何将JAR交由Systemctl管理?
AI越来越火了,我们想要不被淘汰就得主动拥抱。推荐一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站 废话不多说,进入正题。下面开始说如何使用 systemctl…...