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

  在平时的实际项目开发中,我们往往很少会去在意分布式锁,而是依赖于关系型数据库固有的排他性来实现不同进程之间的互斥,但大型分布式系统的性能瓶颈往往集中在数据库操作上。本文我们来看看ZooKeeper如何实现分布式锁,主要讲解排他锁和共享锁两类分布式锁。

排他锁

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

从上面讲解的排它锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到,下面我们就来看看如何借助Zookeeper实现排他锁。

定义锁

通常的Java开发编程中,有两种常见的方式可以用来定义锁,分别是synchronized机制和JDK5提供的ReentrantLock。在ZooKeeper中,是通过ZooKeeper上的数据节点来表示一个锁,如/exclusive_lock/lock节点就可以被定义为一个锁。如下图所示:

获取锁

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

释放锁

在“定义锁”的部分,我们已经提到,/exclusive_lock是一个临时节点,因此在以下两种情况下,可能释放锁:

  • 当前获取锁的客户端机器宕机,那么ZooKeeper上的这个临时节点就会被移除;
  • 正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。

无论什么情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复“获取锁”过程,

整个排他锁的获取和释放流程如下图所示:

共享锁

Shared Locks,简单S锁,同样是一种基本的锁类型。如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其它事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。共享锁和排他锁最根本的区别在于,加上了排他锁后,数据对象只对一个事务可见,而加上了共享锁后,数据对所有事务都可见。下面看如何借助ZooKeeper来实现共享锁。

定义锁

和排他锁一样,同样是通过ZooKeeper上的数据节点来表示一个锁,是一个类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点,如:

/shared_lock/192.168.0.1-R-00000001,那么 这个节点就代表了一个共享锁。

获取锁

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

判断读写顺序

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

  1. 创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册了子节点变更的Wathcher监听
  2. 确定自己的节点序号在所有子节点中的顺序
  3. 对于读请求:

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

如果比自己序号小的子节点中有写请求,那么就需要进入等待;

对于写请求:

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

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

释放锁

释放锁的逻辑和排他锁是一致的,这里不再赘述。整个共享锁的获取可以用下图表示。

羊群效应

上面讲解的这个共享锁实现,大体上能满足一般分布式集群竞争锁的需求,并且性能都还可以先——这里说的一般场景是只集群规模不是特别大,一般10台机器以内。但是机器规模扩大后,会有什么问题呢?我们着重来看上面“判断读写顺序“过程的步骤3,结合下图实例,看看实际运行的情况。

针对如上图所示的情况进行分析

1. 192.168.0.1首先进行读操作,完成后将节点/shared_lock/192.168.0.1-R-00000001删除。

2. 余下4台机器均收到这个节点移除的通知,然后重新从/shared_lock节点上获取一份新的子节点列表。

3. 每台机器判断自己的读写顺序,其中192.168.0.2检测到自己序号最小,于是进行写操作,余下的机器则继续等待。

4. 继续…

可以看到,192.168.0.1客户端在移除自己的共享锁后,Zookeeper发送了子节点更变Watcher通知给所有机器,然而除了给192.168.0.2产生影响外,对其他机器没有任何作用。大量的Watcher通知和子节点列表获取两个操作会重复运行,这样会造成系能鞥影响和网络开销,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或事务中断引起节点小时,Zookeeper服务器就会在短时间内向其他所有客户端发送大量的事件通知,这就是所谓的羊群效应。

可以有如下改动来避免羊群效应。

1. 客户端调用create接口常见类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。

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

3. 如果无法获取共享锁,就调用exist接口来对比自己小的节点注册Watcher。对于读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求:向比自己序号小的最后一个节点注册Watcher监听。

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

此方案改动主要在于:每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。

ZooKeeper典型应用场景:分布式锁的更多相关文章

  1. 搞懂分布式技术6:Zookeeper典型应用场景及实践

    搞懂分布式技术6:Zookeeper典型应用场景及实践 一.ZooKeeper典型应用场景实践 ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了 ...

  2. ZooKeeper典型应用场景

    ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新.例 ...

  3. ZooKeeper典型应用场景一览

    原文地址:http://jm-blog.aliapp.com/?p=1232 ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据 ...

  4. ZooKeeper典型应用场景(转)

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  5. ZooKeeper典型应用场景概览

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  6. ZOOKEEPER典型应用场景解析

    zookeeper实现了主动通知节点变化,原子创建节点,临时节点,按序创建节点等功能.通过以上功能的组合,zookeeper能够在分布式系统中组合出很多上层功能.下面就看几个常用到的场景,及使用方式和 ...

  7. 整理分布式锁:业务场景&分布式锁家族&实现原理

    1.引入业务场景 业务场景一出现: 因为小T刚接手项目,正在吭哧吭哧对熟悉着代码.部署架构.在看代码过程中发现,下单这块代码可能会出现问题,这可是分布式部署的,如果多个用户同时购买同一个商品,就可能导 ...

  8. Zookeeper是如何实现分布式锁的

    [toc] Zookeeper是如何实现分布式锁的 标签 : Zookeeper 分布式 实现分布式锁要考虑的重要问题 1. 三个核心要素 加锁, 解锁, 锁超时 2. 三个问题 要保证原子性操作, ...

  9. zookeeper典型应用场景之一:master选举

    对于zookeeper这种东西,仅仅知道怎么安装是远远不够的,至少要对其几个典型的应用场景进行了解,才能比较全面的知道zk究竟能干啥,怎么玩儿,以后的日子里才能知道这货如何能为我所用.于是,有了如下的 ...

随机推荐

  1. 标签页QTabWidget

    样式: import sys from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QWidget, QTab ...

  2. XML之Well-Formed文档规则

    由于课程原因,近日粗略学习XML,载以博客是为担心忘记,以供日后复习之用. XML标准中明确规定了XML文件应当遵守的规则,大致上分成基本规则和DTD(Document Type Definition ...

  3. luogu P1486 [NOI2004]郁闷的出纳员

    一万年以后终于调过了这题 这道题主要是维护一个有序的集合(吧),所以使用平衡树(我这里用\(Splay\)) 记录一个变量\(ff\)(雾),表示所有工资的变化量 对于\(I\)操作,如果初始工资大于 ...

  4. Linux 流量控制总结(TC)

    TC对带宽的描述:    mbps = 1024 kbps = 1024 * 1024 bps => byte/s    mbit = 1024 kbit => kilo bit/s.   ...

  5. Activity生命周期函数、onSaveInstanceState()和onRestoreInstanceState()的介绍

    http://www.cnblogs.com/tianzhijiexian/p/3885472.html

  6. 根据href给当前导航添加样式

    var href = window.location.href.split('/')[window.location.href.split('/').length-1].substr(0,20); i ...

  7. Servlet3.0的注解自定义原生Servlet实战

    Servlet3.0的注解自定义原生Servlet实战 讲解:使用 Servlet3.0的注解自定义原生Servlet和Listener 自定义原生Servlet package net.xdclas ...

  8. 【windows核心编程】注入DLL时BUG排除与调试

    DLL注入排除bug的思路步骤. 1.在VS中监视输入err,hr检查DLL是否注入成功 2.OD断点loadlibraryW,loadlibraryA是否已经注入成功,eax是否有值. 3.检查路径 ...

  9. [转]Linux下的链接脚本基础

    [转]http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 1. 前言 (1)每一个链接过程都由链接脚本(linke ...

  10. 【bzoj2653】【middle】【主席树+二分答案】

    Description 一个长度为 n 的序列 a ,设其排过序之后为 b ,其中位数定义为 b[n/2] ,其中 a,b 从 0 开始标号 , 除法取下整. 给你一个长度为 n 的序列 s .回答 ...