继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

ZooKeeper 分布式数据结构(四)

艾贺521
关注TA
已关注
手记 292
粉丝 1.1万
获赞 1544

前面说了ZooKeeper一些基础性的东西,包括客户端编程框架。这里我们来探索如何更好的运用ZooKeeper。
开始之前,我想先借用Linus Torvalds(Linux创始人)的一句话。

Bad programmers worry about the code. Good programmers worry about data structures and their relationships
差的程序员关注代码,好的程序员关注数据结构和数据之间的关系。

1000

与文无关

不管如何,我们能感受到数据结构的重要性,而平时我们开发所涉及到的各种东西,本质还是:
程序=算法+数据结构。数据结构也是编程的基础,我们这里说如何通过ZooKeeper构建分布式系统,和它涉及的数据结构。

涉及到的数据结构:

  • Barrir (障碍)

  • Quene (队列)

  • Lock (锁)

  • Leader Selection

  • Group MemberShip

  • Service Discovery

这是一篇介绍技术的文章,但是里面一张图也没有,捂脸...

Barrier

Barrier中文译为栅栏,障碍。意思是暂时阻塞部分东西,直到某个条件满足。比如:“百米赛跑的起跑线,把起跑线当做障碍,大家还不能跑,只有人到齐了才能跑”。意思就是满足某个条件之后才能继续接下来的操作。

可以参考多线程的:CountDownLatch和CyclicBarrir。

使用ZooKeeper算法实现:(使用伪代码)

  1. 首先,创建一个障碍节点 /zk_barrier

  2. 如果障碍节点存在的话,也就是系统中是存在某个障碍,需要满足某个条件的。

  3. 客户端在/zk_barrier节点调用exists()方法,注册watch事件。

    1. 如果exists()方法返回为false,也就是条件满足,客户端可以进行接下来的操作了。

    2. 如果exists()方法返回为true,客户端就等待watch的事件。

  4. 当障碍节点需要的条件满足的时候,负责/zk_barrier节点的客户端会删除这个节点。

  5. 删除节点触发watch事件,客户端再次调用exists()方法再障碍节点上。

  6. 如果exists()返回为true的话,客户端继续原本的操作,等待条件满足。

上面的这个结构相对比较简单,还有种结构用来 锁住分布式计算的开始和结束,也叫做Double Barrier. 双重障碍的实现,当节点的数量满足条件的时候,开始任务,当节点的数量为0的时候,结束任务。

算法实现:
第一部分:

  1. 首先创建一个/barrier节点,其余客户端在障碍节点上注册事件,并且在/barrier节点下面创建临时节点,也就是以/barrier节点为父节点。  新创建的节点一般是客户端自己的主机名。

  2. 客户端同时注册一个事件,检查/barrier/ready节点是否存在。

  3. 系统中已经预定义了 N 数字,这个数目为任务开始的最小满足节点数。节点的数目必须要等于或多与N的时候,才开始任务。

  4. 当有新的节点加入的时候,新的节点都获取一下当前/barrier节点下子节点的数目。注意getChildren没有设置监听事件。
    M = getChildren(/barrier, watch=false)

  5. 如果M小于N,继续等待ready节点的出现。

  6. 如果M等于N,然后这个客户端在/barrier节点下创建/barrier/ready节点。

  7. 由于之前有节点注册了监听事件,每个客户端都会开始任务。
    第二部分:

  8. 客户端完成它需要完成的任务之后,删除它在/barrier节点下创建的子节点。

  9. 删除完之后,客户端再调用
    M = getChildren(/barrier, watch=true)。
    如果M大于0,客户端仍然等待通知。
    如果M等于0,客户端退出。

上面可能有个风险是网络流量过于庞大,一旦ready节点注册,它像所有的节点发送通知。有个办法是每个客户端注册ready的下一个最小的序列临时节点。这样每次只有一个节点收到通知。不会一下子所有的节点都被唤醒。

Queue

分布式队列也是分布式系统中一种非常常见的数据结构,一种比较特殊的队列叫做。生产者-消费者 队列。有一个集合,生产者生产的东西存放在这个集合里面,消费者从集合中拿出东西来消费。它遵守FIFO(先进先出原则)。

直接看伪代码:

  1. 创建一个/QUEUE节点,用作实现队列的集合。

  2. 作为生产者的客户端,调用create()方法创建名为"queue-"的节点,这个节点是临时节点,同时是序列节点,并且是/QUEUE的子节点。一般节点名称像queue-N

  3. 作为消费者的客户端,在/QUEUE节点上调用getChildren()方法,同时设置监听事件。
    M = getChildren(/_QUEUE_, true)
    对子节点排序,拿出数字最小的节点进行消费。然后删除最小的那个节点。
    有可能在删除节点的时候,因为有其它的节点在对这个节点进行访问,导致删除失败,这时候我恩需要再次重试删除

  4. 消费者的客户端不断的从子节点列表中取出节点进行消费,一旦列表中的节点都被消费了。消费者重新调用getChildren方法。

  5. 直到getChildren()返回为空的列表的时候,这意味着/QUEUE节点下没有更多的节点了。

有更多精力的话可以实现优先级队列。

注意:建议看一下ZooKeeper官方对这两种数据结构的实现地址为http://zookeeper.apache.org/doc/r3.4.6/zookeeperTutorial.html

分布式锁

分布式锁也就是意味着在任何时间,系统中最多只能有一个客户端持有这把锁。一般在下面场景的时候有用:

  • 写入共享数据库或文件

  • 处理I/O请求

简单描述一下共享锁:假设在lock-node下同时创建了三个子节点,l1,l2,l3。那么创建l1的子节点拥有锁。当客户端想释放锁的时候,只需要删除l1节点,然后创建l2的客户端就拥有了锁,依次类推。

伪代码实现:
第一部分:获取锁

  1. 在/locknode下创建子节点,这个节点是 临时有序的,create("/_locknode_/lock-",CreateMode=EPHEMERAL_SEQUENTIAL)

  2. 调用getChilren(/locknode/lock-,false)方法,不设置监听器。

  3. 检查获取到的节点列表,如果自己创建的节点拥有最小的序号,那么当前客户端拥有锁。退出算法。

  4. 调用exists("/_locknode_/<最小的节点>, True)方法

  5. 如果exists返回为false,那么重新进行第2步。

  6. 如果exists为true,那么等待存在的这个节点的 watch事件。然后进行第4步。

第二部分:释放锁

  1. 持有锁的客户端删除节点,触发下一个节点获取锁。

  2. 比当前节点序号大的程度最小节点将会获取到锁。

Leader选取

Leader选取算法有两个要求:

  • 大多数时间内,只有一个Leader

  • 在任何时间内,要么没有Leader要么只有一个Leader

暂时我们先把Leader选取算法理解为锁算法,谁拥有了锁,谁就是Leader。关于Leader选取算法还需要深入分析。

Group Membership 成员关系

组员关系也是分布式系统的核心组件,目的是为了维护服务的可靠性和一致性。它可以让组内的任何一个实体知道当前组的状况,谁加入了进来,谁离开了。

实现起来较为简单,伪代码如下:

  1. 创建/Membership持久节点,代表成员关系树

  2. 加入组的节点在/Membership节点下创建临时节点。

  3. 所有的组内成员都有注册/Membership的监听事件,因此可以知悉/Membership节点下的变化

  4. 当有新的节点加入进来,其它的节点会收到通知。当有节点离开,或故障了,ZooKeeper也会自动的删除这个节点,所有的组员也会知道。

  5. 当有节点想知道其它节点的状态。使用getChildren()获取/Membership节点下的子节点即可。

服务发现

如果有认真看上面的几种实现,我想大部分人都该了解ZooKeeper的套路了,服务发现,也就是我们要知道提供某个服务的服务器有哪些。我们去找谁来提供服务。

可以理解为“物以类聚,人以群分”,这样一群一群的,就变成了上面的成员关系管理。数据库服务的一组,web服务的一组。文件服务器的一组,想要知道这个组内的状态,获取这个组的节点就好了。比如web服务器,可以创建/web_service节点,然后web服务都去这个节点找。

其余的就不多说了。和成员关系基本一样

实现

光有理论不去实现怎么行呢,代码贴出来一方面不太好讲解,编程靠的是思想,你把东西想明白了,开发起来才容易,自己都想不明白的事情,又如何指望计算机来帮你处理呢。

关于本次所有涉及的理论部分,实现的地方有两处:

  1. ZooKeeper的github项目实现 https://github.com/apache/zookeeper/tree/master/src/recipes ,它实现了Quene,Lock,Leader Selection

  2. Apache Curaror直接给我们封装好了常用的分布式数据结构,追求快速的话,可以直接使用Apache Curator. maven包为org.apache.curator.curator-recipes,貌似curator也实现了服务发现。感兴趣的都可以自己看。

最后

这次主要还是理论偏多,但是大型项目所依赖的也不过是这些基础的部分。关于ZooKeeper的应用场景,如何使用到这些数据结构的,下面我们再谈。

参考

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP