您好,欢迎来到12图资源库!分享精神,快乐你我!我们只是素材的搬运工!!
  • 首 页
  • 当前位置:首页 > 开发 > WEB开发 >
    基于Zookeeper的散布式锁
    时间:2017-10-24 12:57 来源:网络整理 作者:网络 浏览:收藏 挑错 推荐 打印

    Zookeeper(业界简称zk)是一种提供配置管理、散布式协同以及命名的中心化效劳,这些提供的功用都是散布式系统中十分底层且必不可少的基本功用,但是假设本人完成这些功用而且要到达高吞吐、低延迟同时还要保持分歧性和可用性,实践上十分困难。因此zookeeper提供了这些功用,开发者在zookeeper之上构建本人的各种散布式系统。

    虽然zookeeper的完成比较复杂,但是它提供的模型笼统却是十分复杂的。Zookeeper提供一个多层级的节点命名空间(节点称为znode),每个节点都用一个以斜杠(/)分隔的途径表示,而且每个节点都有父节点(根节点除外),十分相似于文件系统。例如,/foo/doo这个表示一个znode,它的父节点为/foo,父父节点为/,而/为根节点没有父节点。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只要文件节点可以寄存数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存缩小量的数据,每个节点的寄存数据下限为1M。

    而为了保证高可用,zookeeper需求以集群形状来部署,这样只需集群中大部分机器是可用的(可以容忍一定的机器缺点),那么zookeeper本身依然是可用的。客户端在运用zookeeper时,需求知道集群机器列表,经过与集群中的某一台机器树立TCP衔接来运用效劳,客户端运用这个TCP链接来发送央求、获取结果、获取监听事情以及发送心跳包。假设这个衔接异常断开了,客户端可以衔接到另外的机器上。

    架构简图如下所示:

    基于Zookeeper的散布式锁

    客户端的读央求可以被集群中的恣意一台机器处置,假设读央求在节点上注册了监听器,这个监听器也是由所衔接的zookeeper机器来处置。关于写央求,这些央求会同时发给其他zookeeper机器并且达成分歧后,央求才会前往成功。因此,随着zookeeper的集群机器增多,读央求的吞吐会提高但是写央求的吞吐会下降。

    有序性是zookeeper中十分重要的一个特性,一切的更新都是全局有序的,每个更新都有一个独一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读央求只会相关于更新有序,也就是读央求的前往结果中会带有这个zookeeper最新的zxid。

    如何运用zookeeper完成散布式锁?

    在描画算法流程之前,先看下zookeeper中几个关于节点的幽默的性质:

    有序节点:假设以后有一个父节点为/lock,我们可以在这个父节点下面创立子节点;zookeeper提供了一个可选的有序特性,例如我们可以创立子节点“/lock/node-”并且指明有序,那么zookeeper在生成子节点时会依据以后的子节点数量自动添加整数序号,也就是说假设是第一个创立的子节点,那么生成的子节点为/lock/node-0000000000,下一个节点则为/lock/node-0000000001,依次类推。

    暂时节点:客户端可以树立一个暂时节点,在会话完毕或许会话超时后,zookeeper会自动删除该节点。

    事情监听:在读取数据时,我们可以同时对节点设置事情监听,当节点数据或结构变化时,zookeeper会通知客户端。以后zookeeper有如下四种事情:1)节点创立;2)节点删除;3)节点数据修正;4)子节点变更。

    下面描画运用zookeeper完成散布式锁的算法流程,假定锁空间的根节点为/lock:

    客户端衔接zookeeper,并在/lock下创立 暂时的 且 有序的 子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

    客户端获取/lock下的子节点列表,判别本人创立的子节点能否为以后子节点列表中 序号最小 的子节点,假设是则以为取得锁,否则监听/lock的子节点变更音讯,取得子节点变更通知后重复此步骤直至取得锁;

    执行业务代码;

    完成业务流程后,删除对应的子节点释放锁。

    步骤1中创立的暂时节点可以保证在缺点的状况下锁也能被释放,思索这么个场景:假设客户端a以后创立的子节点为序号最小的节点,取得锁之后客户端所在机器宕机了,客户端没有自动删除子节点;假设创立的是永世的节点,那么这个锁永远不会释放,招致死锁;由于创立的是暂时节点,客户端宕机后,过了一定时间zookeeper没有收到客户端的心跳包判别会话失效,将暂时节点删除从而释放锁。

    另外细心的冤家能够会想到,在步骤2中获取子节点列表与设置监听这两步操作的原子性成绩,思索这么个场景:客户端a对应子节点为/lock/lock-0000000000,客户端b对应子节点为/lock/lock-0000000001,客户端b获取子节点列表时发现本人不是序号最小的,但是在设置监听器前客户端a完成业务流程删除了子节点/lock/lock-0000000000,客户端b设置的监听器岂不是丧失了这个事情从而招致永远等候了?这个成绩不存在的。由于zookeeper提供的API中设置监听器的操作与读操作是 原子执行 的,也就是说在读子节点列表时同时设置监听器,保证不会丧失事情。

    最后,关于这个算法有个极大的优化点:假设以后有1000个节点在等候锁,假设取得锁的客户端释放锁时,这1000个客户端都会被唤醒,这种状况称为“羊群效应”;在这种羊群效应中,zookeeper需哀通知1000个客户端,这会阻塞其他的操作,最好的状况应该只唤醒新的最小节点对应的客户端。应该怎样做呢?在设置事情监听时,每个客户端应该对刚好在它之前的子节点设置事情监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-0000000001、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除音讯,序号为2的监听序号为1的子节点删除音讯。

    所以调整后的散布式锁算法流程如下:

    客户端衔接zookeeper,并在/lock下创立 暂时的 且 有序的 子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

    客户端获取/lock下的子节点列表,判别本人创立的子节点能否为以后子节点列表中 序号最小 的子节点,假设是则以为取得锁,否则 监听刚好在本人之前一位的子节点删除音讯 ,取得子节点变更通知后重复此步骤直至取得锁;

    执行业务代码;

    完成业务流程后,删除对应的子节点释放锁。

    Curator的源码剖析

    虽然zookeeper原生客户端暴露的API曾经十分繁复了,但是完成一个散布式锁还是比较费事的…我们可以直接运用 curator 这个开源项目提供的zookeeper散布式锁完成。

    我们只需求引入下面这个包(基于maven):

    <dependency> 

        <groupId>org.apache.curator</groupId> 

        <artifactId>curator-recipes</artifactId> 

        <version>4.0.0</version> 

    </dependency> 

    然后就可以用啦!代码如下:

    public static void main(String[] args) throws Exception { 

        //创立zookeeper的客户端 

        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); 

    (责任编辑:admin)