Zookeeper-应用-分布式锁以及和Redis实现对比

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 前言 因为分布式锁在分布式系统中非常重要,所以把分布式锁的实现从ZooKeeper应用中单独拿出来讲。 关于第二节ZooKeeper实现分布式锁的部分,主要借鉴的《从Paxos到Zookeeper(分布式一致性原理与实践)》—倪超 一书。结合我个人的理解,对内容有所精简。大家如果想了解更细节的内容,可以自行阅读这本书,书中还讲解了ZooKeeper在阿里巴巴的实践与应用,个人觉得不仅能加深对ZooKeeper的认识,还能扩宽知识面。相见恨晚!!!墙裂推荐!!!!为作者打Call


系列文章目录 认识 Zookeeper -基本概念,组成和功能_小王师傅66的博客-CSDN博客


认识 Zookeeper -基本概念,组成和功能_小王师傅66的博客-CSDN博客

zookeeper-集群-选举机制_小王师傅66的博客-CSDN博客


ZooKeeper-集群-ZAB协议与数据同步_小王师傅66的博客-CSDN博客

Zookeeper—应用_小王师傅66的博客-CSDN博客



前言

       因为分布式锁分布式系统中非常重要,所以把分布式锁的实现从ZooKeeper应用中单独拿出来讲。


       关于第二节ZooKeeper实现分布式锁的部分,主要借鉴的《从Paxos到Zookeeper(分布式一致性原理与实践)》—倪超 一书。结合我个人的理解,对内容有所精简。大家如果想了解更细节的内容,可以自行阅读这本书,书中还讲解了ZooKeeper在阿里巴巴的实践与应用,个人觉得不仅能加深对ZooKeeper的认识,还能扩宽知识面。相见恨晚!!!墙裂推荐!!!!为作者打Call



一、分布式锁是什么?



   分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要使用分布式锁了。


(大家可以先看下面的流程图,再来看文字解说)


二、ZooKeeper实现分布式锁

1.排它锁



排他锁(Exclusive Locks, 简称X锁),又称为写锁或独占锁,是一种基本的锁类型。如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许事务T对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排他锁。

       从上面讲解的排他锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且

仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。下面我们就来看看如何借助ZooKeeper实现排他锁。


1.1定义锁

       通过ZooKeeper上的数据节点来表示一个锁 ,例如/exclusive_ lock/lock节点就可以被定义为一个锁,如图所示


5850acca854743e9be94fccb9831fefc.png




1.2 获取锁


在需要获取排他锁时,所有的客户端都会试图通过调用create()接口, 在/exclusive_ lock 节点下创建临时子节点/exclusive_ lock/lock,在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端就需到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。

1.3 释放锁


/exclusive_ lock/lock 是一个临时节点,因此在以下两种情况下,都有可能释放锁。

       1.当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除。

       2.正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。

       无论在什么情况下移除了lock 节点,ZooKeeper 都会通知所有在/exclusive_ lock 节点上注

册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复“获取锁”过程。整个排他锁的获取和释放流程,如图所示:



37c3f590ac6f4af9be6dd8763271d30e.png


2.共享锁


共享锁(Shared Locks,简称S锁),又称为读锁,同样是一种基本的锁类型。如果事务T对数据对象O加上了共享锁,那么当前事务只能对O进行读取操作,其他事务也只能对这个数据对象加共享锁一直到该数据对象上的所有共享锁都被释放。

       共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加

上共享锁后,数据对所有事务都可见。下面我们就来看看如何借助ZooKeeper来实现共享锁。


1.1 定义锁


和排他锁一样,同样是通过ZooKeeper上的数据节点来表示一个锁,是一个类似于

“/shared_ lock/[Hostname]-请求类型-序号”的临时顺序节点,例如/shared_ lock/192.168.0.1-R-0000000001,那么,这个节点就代表了一个共享锁,如图所示。


9cbb751c7c034b81b259d87f27403724.png


1.2 获取锁

       在需要获取共享锁时,所有客户端都会到/shared_lock这个节点下面创建一个临时顺序节点,如果当前是读请求,那么就创建例如/shared_ lock/192.168.0.1-R-000000001的节点;如果是写请求,那么就创建例如/shared_ lock/192.168.0.1-W-0000000001的节点。


1.3 判断读写顺序


根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作须在当前没有任何事务进行读写操作的情况下进行。基于这个原则,我们来看看如何通ZooKeeper的节点来确定分布式读写顺序,大致可以分为如下4个步骤。

       1.创建完节点后,获取/shared_ lock节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。


       2.确定自己的节点序号在所有子节点中的顺序。


       3.对于读请求:

       如果没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑。如果比自己序号小的子节点中有写请求,那么就需要进入等待。

       对于写请求:

       如果自己不是序号最小的子节点,那么就需要进入等待。


       4.接收到Watcher通知后,重复步骤1。


1.4 释放锁

       释放锁的逻辑和排他锁是一致的,这里不再赘述。整个共享锁的获取和释放流程,如图所示:


84809731f558426eb008f99f17ef9021.png


(实在画不动了,把作者的图搬来了)

羊群效应


上面讲解的这个共享锁实现,大体上能够满足一般的分布式集群竞争锁的需求,并且性能都还可以。这里说的一般场景是指集群规模不是特别大,一般是在10台机器以内。但是如果机器规模扩大之后,会有什么问题呢?


       当192.168.0.1 这台机器完成读操作后将节点/192.168.0.1- R-0000000001删除,余下的4台机器均收到了这个节点被移除的通知,然后重新从/shared_lock节点上获取一份新的子节点列表。每个机器判断自己的读写顺序。其中192.168.0.2这台机器检测到自己已经是序号最小的机器了,于是开始进行写操作,而余下的其他机器发现没有轮到自己进行读取或更新操作,于是继续等待。        


       我们发现,第一个节点移除,只影响下一个节点,对剩下的节点没有影响,但却通知给了所有节点,对剩下的这些节点来说,这个事件通知跟自己是无关的。如果在集群规模比较大的情况下,不仅会对ZooKeeper服务器造成巨大的性能影响和网络冲击,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知一这就是所谓的羊群效应。


       分布式锁竞争过程,它的核心逻辑在于:判断自己是否是所有子节点中序号最小的。于是,很容易可以联想到,解决羊群效应的关键是,每个节点对应的客户端只需要关注比自己序号小的那个相关节点的变更情况就可以了,而不需要关注全局的子列表变更情况。


       所以优化后的实现分布式锁的方案是:


       1.客户端调用create()方法创建一个类似于“/shared_ lock/[Hostname]-请求类型-序号”的临时顺序节点。

       2.客户端调用getChildren()接口来获取所有已经创建的子节点列表,注意,这里不注册任何Watcher。

       3.如果无法获取共享锁,那么就调用exist()来对比自己小的那个节点注册Watcher。

       注意,这里“比自己小的节点”只是一个笼统的说法,具体对于读请求和写请求不一样。

       读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。

       写请求:向比自己序号小的最后一个节点注册Watcher监听

       4.等待Watcher通知,继续进入步骤2。


改造后的共享锁流程图:



b680aae4b5ce4d8a9b9cc14a779f4555.png


(还是直接搬的作者的图)


       和我们实现多线程并发编程的逻辑相似,尽可能减小锁的粒度,提高系统的并发性能。但很明显,改造后的实现流程比改造前复杂了,在实际实现过程中,大家可以根据业务场景和集群规模来选择适合自己的分布式锁实现:在集群规模不大、网络资源丰富的情况下,第一种分布式锁实现方式是简单实用的选择;而如果集群规模达到一定程度,并且希望能够精细化地控制分布式锁机制,那么不妨试试改进版的分布式锁实现。


三、ZooKeeper与Redis实现分布式锁对比

       在 Redis-分布式锁_小王师傅66的博客-CSDN博客 文章中,我们讲了了Redis分布式锁。上面也分享了ZooKeeper分布式锁,我们知道了Redis主要是通过setnx命令实现分布式锁,Zookeeper采用临时节点和事件监听机制可以实现分布式锁。那么这两种方式有哪些关键的区别呢?


1、Redis分布式锁,获取不到锁时,需要不断轮询去尝试获取锁,比较消耗性能;ZooKeeper分布式锁,获取不到锁时,注册监听器即可,不需要不断主动尝试获取锁,性能开销较小;


2、锁未释放时服务器宕机。Redis只能等超时时间到将锁释放。ZooKeeper的临时节点检测不到服务器的心跳,节点移除,锁自动被释放;


       这样看好像ZooKeeper比Redis更胜一筹,但Redis提供的API和库更加丰富,在很大程度上能够减少开发的工作量。而且如果是小规模的项目,已经部署了Redis,可能没太大必要去再部署一套ZooKeeper集群去实现分布式锁,大家根据场景自行选择。

总结

       在分布式环境中,使用分布式锁的场景非常多,ZooKeeper的监听机制和临时节点特性可以帮助我们实现强一致的分布式锁功能。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
15天前
|
Linux 网络安全 Docker
尼恩一键开发环境: vagrant+java+springcloud+redis+zookeeper镜像下载(&制作详解)
尼恩提供了一系列文章,旨在帮助开发者轻松搭建一键开发环境,涵盖Java分布式、高并发场景下的多种技术组件安装与配置。内容包括但不限于Windows和CentOS虚拟机的安装与排坑指南、MySQL、Kafka、Redis、Zookeeper等关键组件在Linux环境下的部署教程,并附带详细的视频指导。此外,还特别介绍了Vagrant这一虚拟环境部署工具,
尼恩一键开发环境: vagrant+java+springcloud+redis+zookeeper镜像下载(&制作详解)
|
8天前
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
431 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
11天前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
136 83
|
7天前
|
缓存 NoSQL 搜索推荐
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
34 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
|
1天前
|
机器学习/深度学习 存储
DeepSeek进阶开发与应用4:DeepSeek中的分布式训练技术
随着深度学习模型和数据集规模的扩大,单机训练已无法满足需求,分布式训练技术应运而生。DeepSeek框架支持数据并行和模型并行两种模式,通过将计算任务分配到多个节点上并行执行,显著提高训练效率。本文介绍DeepSeek中的分布式训练技术,包括配置与启动方法,帮助用户轻松实现大规模模型训练。数据并行通过`MirroredStrategy`同步梯度,适用于大多数模型;模型并行则通过`ParameterServerStrategy`异步处理大模型。DeepSeek简化了分布式环境配置,支持单机多卡和多机多卡等场景。
|
2月前
|
存储 SpringCloudAlibaba Java
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论。
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
|
3月前
|
存储 运维 NoSQL
分布式读写锁的奥义:上古世代 ZooKeeper 的进击
本文作者将介绍女娲对社区 ZooKeeper 在分布式读写锁实践细节上的思考,希望帮助大家理解分布式读写锁背后的原理。
120 11
|
2月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
232 5
|
19天前
|
缓存 NoSQL 中间件
Redis,分布式缓存演化之路
本文介绍了基于Redis的分布式缓存演化,探讨了分布式锁和缓存一致性问题及其解决方案。首先分析了本地缓存和分布式缓存的区别与优劣,接着深入讲解了分布式远程缓存带来的并发、缓存失效(穿透、雪崩、击穿)等问题及应对策略。文章还详细描述了如何使用Redis实现分布式锁,确保高并发场景下的数据一致性和系统稳定性。最后,通过双写模式和失效模式讨论了缓存一致性问题,并提出了多种解决方案,如引入Canal中间件等。希望这些内容能为读者在设计分布式缓存系统时提供有价值的参考。感谢您的阅读!
110 6
Redis,分布式缓存演化之路
|
2月前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
194 85