zookeeper是一个树形结构,相似于前端开发中的 tree.js 组件;node
zk的数据模型也能够理解为 linux/unix 的文件目录:/usr/locallinux
每个节点都称之为 znode,它能够有子节点,也能够有数据git
子节点: 就是父目录下的一个子目录,在 zk中称之为节点,每个节点中都有一些相应的数据,就像目录下有一些文件数据同样github
每一个节点分为临时节点和永久节点,临时节点在客户端断开后消失数据库
永久节点:其实就是一个持久化的过程,咱们存了一些数据,只有人为的状况在才会删除 如是session超时或者是session丢失,数据仍是会一直存在的。临时节点生命周期依赖建立它的会话,一旦会话结束,临时节点将会被删除。临时节点不容许有子节点。设计模式
每一个zk节点都是有各自的版本号,能够经过命令行来显示节点信息;session
节点的信息其实就是节点的详情,详情中包含了一些版本号,版本号是累加的,每当节点中的数据发生变化,版本号就会累加(乐观锁)并发
删除/修改过期节点,版本号不匹配则会报错框架
例如咱们在查询某一个节点的时候,好比说它的节点是1,通过两我的进行删除或者修改以后,那么它的节点会由1变为2,在变为3,此时咱们须要去修改或者删除这个节点,那么删除这个节点的时候,咱们传入的版本号是一个老的版本号,那么这个时候就会报一个版本号不匹配的异常,这也是在数据库中是使用乐观锁的一种表现
每个zk节点存储的数据不宜过大,几k便可(官方推荐);
节点是能够设置权限acl ,能够经过权限来限定用户的访问
acl:权限控制列表,后续会讲到;
在分布式系统中,Master每每用来协调集群中其余系统单元,具备对分布式系统状态变动的决定权,如在读写分离的应用场景中,客户端的写请求每每是由Master来处理,或者其经常处理一些复杂的逻辑并将处理结果同步给其余系统单元。利用Zookeeper的强一致性,可以很好地保证在分布式高并发状况下节点的建立必定可以保证全局惟一性,即Zookeeper将会保证客户端没法重复建立一个已经存在的数据节点。
首先建立/master_election/2019-07-05节点,客户端集群天天会定时往该节点下建立临时节点,如/master_election/2019-07-05/binding,这个过程当中,只有一个客户端可以成功建立,此时其变成master,其余节点都会在节点/master_election/2019-07-05上注册一个子节点变动的Watcher,用于监控当前的Master机器是否存活,一旦发现当前Master挂了,其他客户端将会从新进行Master选举。
这也就是所谓的首脑模式,从而保证咱们的集群是高可用的;
数据发布/订阅系统,即配置中心。须要发布者将数据发布到Zookeeper的节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。发布/订阅通常有两种设计模式:推模式和拉模式,服务端主动将数据更新发送给全部订阅的客户端称为推模式;客户端主动请求获取最新数据称为拉模式,Zookeeper采用了推拉相结合的模式,客户端向服务端注册本身须要关注的节点,一旦该节点数据发生变动,那么服务端就会向相应的客户端推送Watcher事件通知,客户端接收到此通知后,主动到服务端获取最新的数据。
若将配置信息存放到Zookeeper上进行集中管理,在一般状况下,应用在启动时会主动到Zookeeper服务端上进行一次配置信息的获取,同时,在指定节点上注册一个Watcher监听,这样在配置信息发生变动,服务端都会实时通知全部订阅的客户端,从而达到实时获取最新配置的目的。
Dubbo是集团开源的分布式服务框架,致力于提供高性能和透明化的远程服务调用解决方案和基于服务框架展开的完整SOA服务治理方案。
其中服务自动发现是最核心的模块之一,该模块提供基于注册中心的目录服务,使服务消费方可以动态的查找服务提供方,让服务地址透明化,同时服务提供方能够平滑的对机器进行扩容和缩容,其注册中心能够基于提供的外部接口来实现各类不一样类型的注册中心,例如数据库、ZK和Redis等。接下来看一下基于ZK实现的Dubbo注册中心。
/dubbo: 这是Dubbo在ZK上建立的根节点。
/dubbo/com.foo.BarService:这是服务节点,表明了Dubbo的一个服务。
/dubbo/com.foo.BarService/Providers:这是服务提供者的根节点,其子节点表明了每一个服务的真正的提供者。
/dubbo/com.foo.BarService/Comsumers:这是服务消费者的根节点,其子节点表明了每个服务的真正的消费者。
Dubbo基于ZK实现注册中心的工做流程:
服务提供者:在初始化启动的时候首先在/dubbo/com.foo.BarService/Providers节点下建立一个子节点,同时写入本身的URL地址,表明这个服务的一个提供者。
服务消费者 : 在启动的时候读图并订阅zookeeper上/dubbo/com.foo.BarService/Providers节点下的全部节点,并解析全部提供者的URL地址做为该服务类的地址列表,开始发起正常的调用。同时在Consumers节点下建立一个临时节点,写入本身的URL地址,表明本身是BarService的一个消费者
监控中心 : 监控中心是Dubbo服务治理体系的重要一部分,它须要知道一个服务的全部提供者和订阅者及变化状况。监控中心在启动的时候会经过ZK的/dubbo/com.foo.BarService节点来获取全部提供者和消费者的url地址,并注册Watcher来监听其子节点变化状况。
全部服务提供者在ZK上建立的节点都是临时节点,利用的是临时节点的生命周期和客户端会话绑定的特性,一旦提供者机器挂掉没法对外提供服务时该临时节点就会从ZK上摘除,这样服务消费者和监控中心都能感知到服务提供者的变化。
命名服务也是分布式系统中比较常见的一类场景,被命名的实体一般能够是集群中的机器、提供的服务地址或远程对象,其中较为常见的是一些分布式服务框架中的服务地址列表,经过使用命名服务客户端应用可以制定名字来获取资源的实体、服务地址和提供者的信息等。
上层应用使用命名服务时可能仅须要一个全局惟一的名字,相似于数据库中的惟一主键,用数据库自增id是能够的,但分库分表的状况下就没法依靠数据库的自增属性来惟一标识一条记录了。另外UUID也是一种普遍应用的ID实现方式,但若是是用UUID对服务进行命名的话就太不直观了,从字面意思根本看不出其表达的含义。下面看下用ZK如何实现全局惟一ID的生成。
以前在ZNode介绍时提过,建立节点时能够设定为SEQUENTIAL顺序节点,建立后API会返回这个节点的完整名字,利用这个特性咱们就能够来生成全局惟一ID了。
全部客户端根据本身的任务类型,在指定类型的任务下建立一个顺序节点,例如“Job-”节点
节点建立完毕后会返回一个完整的节点名称,如Job-0000000001
客户端拿到这个返回值后拼接上type类型,例如type1-Job-000000001,这样就能够做为一个全局惟一的ID了
在ZK中每一个数据节点都能维护一份子节点的顺序序列,当客户端对其建立一个顺序子节点时ZK会自动之后缀的形式在其子节点上添加一个序号,该场景就利用了ZK的这个特性。
分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,能够保证不一样系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。
排它锁又称为写锁或独占锁,若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只容许事务T1对O1进行读取和更新操做,其余任何事务都不能再对这个数据对象进行任何类型的操做,直到T1释放了排它锁。
若是不一样系统或同一系统不一样机器之间共享了同一资源,那访问这些资源时一般须要一些互斥手段来保证一致性,这种状况下就须要用到分布式锁了。
使用关系型数据库是一种简单、普遍的实现方案,但大多数大型分布式系统中数据库已是性能瓶颈了,若是再给数据库添加额外的锁会更加不堪重负;另外,使用数据库作分布式锁,当抢到锁的机器挂掉的话如何释放锁也是个头疼的问题。
接下来看下使用ZK如何实现排他锁。排他锁的核心是如何保证当前有且只有一个事务得到锁,而且锁被释放后全部等待获取锁的事务可以被通知到。
获取锁,在须要获取排它锁时,全部客户端经过调用接口,在/exclusive_lock节点下建立临时子节点/exclusive_lock/lock。Zookeeper能够保证只有一个客户端可以建立成功,没有成功的客户端须要注册/exclusive_lock节点监听。
释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会致使临时节点的删除,此时,全部在/exclusive_lock节点上注册监听的客户端都会收到通知,能够从新发起分布式锁获取。
共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操做,其余事务也只能对这个数据对象加共享锁,直到该数据对象上的全部共享锁都被释放。
获取锁: 在须要获取共享锁时,全部客户端都会到/shared_lock下面建立一个临时顺序节点,若是是读请求,那么就建立例如/shared_lock/host1-R-00000001的节点,若是是写请求,那么就建立例如/shared_lock/host2-W-00000002的节点。
判断读写顺序:不一样事务能够同时对一个数据对象进行读写操做,而更新操做必须在当前没有任何事务进行读写状况下进行,经过zookeeper来肯定分布式读写顺序,大体分为四步。
建立完节点后,获取/shared_lock节点下全部字节点,并对该节点变动注册监听。
肯定本身的节点序号在全部子节点中的顺序
对于读请求:若没有比本身序号小的子节点或者全部比本身序号小的子节点都是读请求,那么代表本身已经成功获取共享锁,同时开始执行读取逻辑,如有写请求,则须要等待。对于写请求:若本身不是序号最小的子节点,那么须要等待。
接收到Watcher通知后,重复步骤1.
释放锁:其释放锁的流程与独占锁的流程一致。