CRUD工程师进阶篇——Zookeeper

上回书说到Eureka已经停更了,然后大部分公司要么Nacos要么Zookeeper。所有Zookeeper是需要进行学习的。
插一个题外话,Eureka2.0就猝死了,有点早......,可能是悄悄的闭源了。
首先还是先介绍下Zookeeper
ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
其实可以很明显的看出这些东西,Eureka都能完成。就介绍一些Eureka中没有介绍到的东西。
ZooKeeper中的流程:
Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。可以看出这个流程其实和Eureka没有什么区别。
这边再介绍下Zookeeper的角色:
到这边就会有一个疑问,为什么需要角色模式?其实目前看完大概的网上解答,这个让我想起了大学时候学的一门课程叫《无线传感器网络》这里就是一个个节点然后根据不同的算法,但大致的算法都是一个思想,按照簇分类,选举领导。
其实是可以反过来进行思考的,Zookeeper很多时候是运用于大数据领域,所以需要一定的吞吐量能力,而心跳机制可能并不是很适合这个。
所以进行角色分配就可以使得系统更加的健壮,也就是说当大量操作同时发生的时候,每一个螺丝钉都只会自己的事情。就好比人类社会的工人一样。

 

通过这个可以看得出最累就是Learner属于机器人阶级。

 Leader作为整个ZooKeeper集群的主节点,负责响应所有对ZooKeeper状态变更的请求。它会将每个状态更新请求进行排序和编号,以便保证整个集群内部消息处理的FIFO。对于exists,getData,getChildren等只读请求,收到该请求的zk服务器将会在本地处理,由ZAB理论可知,每个服务器看到的名字空间内容都是一致的,无所谓在哪台机器上读取数据,因此如果ZooKeeper集群的负载是读多写少,并且读请求分布得均衡的话,效率是很高的。对于create,setData,delete等有写操作的请求,则需要统一转发给leader处理,leader需要决定编号、执行操作,这个过程称为一个事务(transaction)。

这里要提到一下ZAB协议,简单的说一下就是ZooKeeper使用的消息系统提供了以下特殊保证:
1.可靠传输:如果消息m被一台服务器送达,它最终会被送达到所有服务器
2.全序:如果一台服务器上消息a在消息b前送达,那么在所有服务器上a将比b先送达。如果a和b是已传输过的消息,那么要么a在b前送达,要么b在a前送达(即不可能有同时发生的情况)
3.因果序:如果一个发送者在消息a送达后再发送消息b,那么a必须排在b之前。如果发送者在送达b后再发送消息c,那么c必须排在b之后
 ZooKeeper消息协议中有以下元素:
:通过FIFO通道传送的byte序列
提案:一种共同协议。协议需要法定人数的ZooKeeper服务器来通过。大部分提案可以包含消息,但是像NEW_LEADER这样的提案是不包含消息的
消息:原子地广播到所有ZooKeeper服务器的byte序列

ZooKeeper保证消息的全序,同时它也保证提案的全序。ZooKeeper使用一种ZooKeeper事务id(zxid)来暴露整体顺序。所有提案在提出时均会被打上一个zxid标记,它代表了提案的整体顺序。zxid由两部分组成:周期(epoch)和计数器(counter)。在当前的实现中zxid是一个64位整数,高32位为epoch,低32位为counter,因此zxid也可以记为一个整数对(epoch, count)。epoch的值代表leader的改变,每当选举产生一个新的leader就会生成一个它独有的epoch编号。ZooKeeper使用了一种简单的算法将一个唯一的zxid赋给提案:leader对每个提案只是简单地递增zxid以得到一个唯一的zxid值。Leader激活算法会保证只有一个leader使用一个特定的epoch,因此这个简单的算法可以保证每个提案都有一个唯一的id。提案被发送到所有的ZooKeeper服务器,在它们中的法定人数认可该提案后其被提交。如果该提案还包含一个消息,那么在提案被提交时消息被送达。“认可”意味着服务器已将提案保存到持久化存储上。“法定人数”代表一组服务器,必须满足任意两个法定人数对之间至少有一个共同的服务器。因此典型情况下,任意一个法定人数至少有(n/2+1)台服务器即可满足要求,这里n是ZooKeeper服务中的总服务器数。

Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
发现:要求zookeeper集群必须选举出一个 Leader 进程,同时 Leader 会维护一个 Follower 可用客户端列表。将来客户端可以和这些 Follower节点进行通信。
同步:Leader 要负责将本身的数据与 Follower 完成同步,做到多副本存储。这样也是提现了CAP中的高可用和分区容错。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中
广播:Leader 可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的 Follower。
每个Server在工作过程中有三种状态:
LOOKING:当前Server不知道leader是谁,正在搜寻
LEADING:当前Server即为选举出来的leader
FOLLOWING:leader已经选举出来,当前Server与之同步
也就说角色是为了实现CAP中的AP

Zookeeper节点数据操作流程

 

1.在Client向Follwer发出一个写的请求

2.Follwer把请求发送给Leader

3.Leader接收到以后开始发起投票并通知Follwer进行投票

4.Follwer把投票结果发送给Leader

5.Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader,然后commit;

6.Follwer把请求结果返回给Client


至于选举这边只要半数通过就OK,但是有一个奇特的点就是处于严谨性,例如一共4台。已经3台同意了,但是依然会继续问 是否同意第二台做leader。

Zookeeper的节点:
zookeeper 中节点叫znode存储结构上跟文件系统类似,以树级结构进行存储。不同之外在于znode没有目录的概念,不能执行类似cd之类的命令。znode结构包含如下:path,childNode,stat,type

 

 

 


监听机制
监听节点的数据变化事件包括:1、节点被创建; 2、节点上写入数据; 3、节点数据变化; 4、节点数据被删除; 5、节点本身被删除。
1) server端
在一个server启动时,如tomcat启动时,可以把在tomcat启动程序中,把当前tomcat服务写入到zookeeper 的 znode中(临时节点);
2) client端
如某一个client需要使用到server端的服务时,可以通过zookeeper的client api去创建到server端znode节点的连接,并且监听这个节点(创建节点时,可以指出对应的watcher),这样当server(tomcat)宕掉了,会调用监听该节点(znode) client的watcher方法,从而client进行相应的操作;
就是说通过zookeeper 提供的api,可以在client对zookeeper的节点进行监听,当znode发生变化时,会通知client端的watcher,进行watcher的调用;
作用
1、监听服务器在线情况。当服务器启动时,往zookeeper中创建一个临时节点,通过监听这些节点,可以知道服务器在线情况;
2、操作完成时通知。当某一个复杂的操作开始时,往zookeeper创建一个节点,操作完成时,把这个节点删掉。监听方可收到操作通知;
3、分布式队列。每来一个操作,先在zookeeper上创建节点,监听之前存在的节点,之间的节点被删完后相当于排队轮到了自己,自己操作完后删除掉自己的节点。
4、分布式锁。和分布式队列相似。

最后重点讲一下Zookeeper分布式锁:
首先,Zookeeper的每一个节点,都是一个天然的顺序发号器。
在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一
比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。
其次,Zookeeper节点的递增性,可以规定节点编号最小的那个获得锁。
一个zookeeper分布式锁,首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程都会在这个节点下创建个临时顺序节点,由于序号的递增性,可以规定排号最小的那个获得锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。
第三,Zookeeper的节点监听机制,可以保障占有锁的方式有序而且高效。
每个线程抢占锁之前,先抢号创建自己的ZNode。同样,释放锁的时候,就需要删除抢号的Znode。抢号成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。当前一个Znode 删除的时候,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后。
Zookeeper的节点监听机制,可以说能够非常完美的,实现这种击鼓传花似的信息传递。具体的方法是,每一个等通知的Znode节点,只需要监听linsten或者 watch 监视排号在自己前面那个,而且紧挨在自己前面的那个节点。 只要上一个节点被删除了,就进行再一次判断,看看自己是不是序号最小的那个节点,如果是,则获得锁。

释放锁主要有两个工作:

(1)减少重入锁的计数,如果不是0,直接返回,表示成功的释放了一次;

(2)如果计数器为0,移除Watchers监听器,并且删除创建的Znode临时节点;


 

 

posted @ 2020-06-20 22:24  smartcat994  阅读(191)  评论(0编辑  收藏  举报