zookeeper(简称zk),顾名思义,为动物园管理员的意思,动物对应服务节点,zk是这些节点的管理者。
在分布式场景中,zk的应用很是普遍。 zk的一致性,是指数据在多个副本之间保持一致的特性
CAP定理告诉咱们,在分布式系统设计中,P(分区容错性)是不可缺乏的,所以只能在A(可用性)与C(一致性)间作取舍。 zookeeper是CPhtml
数据节点(dataNode):zk数据模型中的最小数据单元,数据模型是一棵树,由斜杠(/)分割的路径名惟一标识,数据节点能够存储数据内容及一系列属性信息,同时还能够挂载子节点,构成一个层次化的命名空间。java
会话(Session):指zk客户端与zk服务器之间的会话,在zk中,会话是经过客户端和服务器之间的一个TCP长链接来实现的。经过这个长链接,客户端可以使用心跳检测与服务器保持有效的会话,也能向服务器发送请求并接收响应,还可接收服务器的Watcher事件通知。Session的sessionTimeout,是会话超时时间,若是这段时间内,客户端未与服务器发生任何沟通(心跳或请求),服务器端会清除该session数据,客户端的TCP长链接将不可用,这种状况下,客户端须要从新实例化一个Zookeeper对象。node
事务及ZXID:事务是指可以改变Zookeeper服务器状态的操做,通常包括数据节点的建立与删除、数据节点内容更新和客户端会话建立与失效等操做。对于每一个事务请求,zk都会为其分配一个全局惟一的事务ID,即ZXID,是一个64位的数字,高32位表示该事务发生的集群选举周期(集群每发生一次leader选举,值加1),低32位表示该事务在当前选择周期内的递增次序(leader每处理一个事务请求,值加1,发生一次leader选择,低32位要清0)。 事务日志:全部事务操做都是须要记录到日志文件中的,可经过 dataLogDir配置文件目录,文件是以写入的第一条事务zxid为后缀,方便后续的定位查找。zk会采起“磁盘空间预分配”的策略,来避免磁盘Seek频率,提高zk服务器对事务请求的影响能力。默认设置下,每次事务日志写入操做都会实时刷入磁盘,也能够设置成非实时(写到内存文件流,定时批量写入磁盘),但那样断电时会带来丢失数据的风险。算法
数据快照:数据快照是zk数据存储中另外一个很是核心的运行机制。数据快照用来记录zk服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中,可经过dataDir配置文件目录。可配置参数snapCount,设置两次快照之间的事务操做个数,zk节点记录完事务日志时,会统计判断是否须要作数据快照(距离上次快照,事务操做次数等于snapCount/2~snapCount 中的某个值时,会触发快照生成操做,随机值是为了不全部节点同时生成快照,致使集群影响缓慢)。数据库
过半:所谓“过半”是指大于集群机器数量的一半,即大于或等于(n/2+1),此处的“集群机器数量”不包括observer角色节点。leader广播一个事务消息后,当收到半数以上的ack信息时,就认为集群中全部节点都收到了消息,而后leader就不须要再等待剩余节点的ack,直接广播commit消息,提交事务。选举中的投票提议及数据同步时,也是如此,leader不须要等到全部learner节点的反馈,只要收到过半的反馈就可进行下一步操做。apache
zk集群由多个节点组成,其中有且仅有一个leader,处理全部事务请求;follower及observer统称learner。learner须要同步leader的数据。follower还参与选举及事务决策过程。zk客户端会打散配置文件中的serverAddress 顺序并随机组成新的list,而后循环按序取一个服务器地址进行链接,直到成功。follower及observer会将事务请求转交给leader处理。api
ZAB(ZooKeeper Atomic Broadcast)是为ZooKeeper设计的一种支持崩溃恢复的原子广播协议。bash
包括崩溃恢复(选主+数据同步)和消息广播(事务操做)。服务器
当leader崩溃或者leader失去大多数的follower 进入恢复模式session
半数经过 – 3台机器 挂一台 2>3/2– 4台机器 挂2台 2!>4/2
(1)设置状态为LOOKING,初始化内部投票Vote (id,zxid) 数据至内存,并将其广播到集群其它节点。节点首次投票都是选举本身做为leader,将自身的服务ID、处理的最近一个事务请求的ZXID(ZXID是从内存数据库里取的,即该节点最近一个完成commit的事务id)及当前状态广播出去。而后进入循环等待及处理其它节点的投票信息的流程中。
(2)循环等待流程中,节点每收到一个外部的Vote信息,都须要将其与本身内存Vote数据进行PK,规则为取ZXID大的,若ZXID相等,则取ID大的那个投票。若外部投票胜选,节点须要将该选票覆盖以前的内存Vote数据,并再次广播出去;同时还要统计是否有过半的赞同者与新的内存投票数据一致,无则继续循环等待新的投票,有则须要判断leader是否在赞同者之中,在则退出循环,选举结束,根据选举结果及各自角色切换状态,leader切换成LEADING、follower切换到FOLLOWING、observer切换到OBSERVING状态
一旦leader已经和多数的follower进行了状态同步后,他就能够开始广播消息了,即进入广播状态。
这时候当一个server加入zookeeper服务中,它会在恢复模式下
启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。
Zookeeper服务一直维持在Broadcast状态,直到leader崩溃
了或者leader失去了大部分的followers支持
。
zk经常使用于命名服务、配置管理、集群管理、分布式锁、队列管理
名字服务
这个主要是做为分布式命名服务,利用zookeeper的文件系统,经过调用zk的create node api,可以很容易建立一个全局惟一的path,这个path就能够做为一个名称。
命名服务是指经过指定的名字来获取资源或者服务的地址,提供者的信息。利用Zookeeper很容易建立一个全局的路径,而这个路径就能够做为一个名字,它能够指向集群中的集群,提供的服务的地址,远程对象等。简单来讲使用Zookeeper作命名服务就是用路径做为名字,路径上的数据就是其名字指向的实体。
服务提供者在启动的时候,向ZK上的指定节点(如/{serviceName}/providers)目录下写入本身的URL地址,这个操做就完成了服务的发布。
服务消费者启动的时候,订阅/{serviceName}/providers/目录下的提供者URL地址(若是一次性使用直接读取便可),并向/{serviceName}/consumers/目录下写入本身的URL地址(若是服务提供者者须要经过ZK获取服务消费者身份,可选)。
全部向ZK上注册的地址都是临时节点,这样就可以保证服务提供者和消费者可以自动感应资源的变化。
如dubbo作的就是上面这部分操做
nameservice:
服务提供者:
nameservice -m provider -n ServiceDemo -s 172.17.0.36:2181
服务消费者:
nameservice -m consumer -n ServiceDemo -s 172.17.0.36:2181
服务监控者:
nameservice -m monitor -n ServiceDemo -s 172.17.0.36:2181
复制代码
第一条命令启动服务提供者,它提供一个ServiceDemo的服务,首次启动后会建立/NameService/、/NameService/ServiceDemo/、/NameService/ServiceDemo/provider/几个路径(永久节点)。而后在服务提供进程在/NameService/ServiceDemo/provider/下建立临时序列节点。
第二条命令启动一个服务消费进程,它在/NameService/ServiceDemo/consumer/下建立临时序列节点,并watch /NameService/ServiceDemo/provider/下的子节点变化事件,及时更新provider列表。
第三条命令是启动一个服务监控进程,它watch /NameService/ServiceDemo/provider,/NameService/ServiceDemo/consumer/两个路径的子节点变化,及时更新provider列表和comsumer列表。
经过zookeeper的监听功能 实现配置管理 common.yml改了,也不须要系统A、B、C重启。
系统A、B、C监听着ZooKeeper的节点,一旦common.yml内容有变化,及时响应
参考 : 基于zookeeper实现统一配置管理
利用ZooKeeper的两大特性,就能够实现另外一种集群机器存活性监控的系统。例如,监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操做,就会在/clusterServers节点下建立一个临时节点:/clusterServer/[Hostname]。这样一来,监控系统就可以实时检测到机器的变更状况,至于后续处理就是监控系统的业务了。
zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:
这个算法有个极大的优化点:假如当前有1000个节点在等待锁,若是得到锁的客户端释放锁时,这1000个客户端都会被唤醒,这种状况称为“羊群效应”;在这种羊群效应中,zookeeper须要通知1000个客户端,这会阻塞其余的操做,最好的状况应该只唤醒新的最小节点对应的客户端。应该怎么作呢?在设置事件监听时,每一个客户端应该对恰好在它以前的子节点设置事件监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-000000000一、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。 因此调整后的分布式锁算法流程以下:
咱们这里直接使用curator这个开源项目提供的zookeeper分布式锁实现
<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);
CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
client.start();
//建立分布式锁, 锁空间的根节点路径为/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
mutex.acquire();
//得到了锁, 进行业务流程
System.out.println("Enter mutex");
//完成业务流程, 释放锁
mutex.release();
//关闭客户端
client.close();
}
复制代码
Zookeeper能够处理两种类型的队列: