@boothsun
2018-05-01T16:06:04.000000Z
字数 2985
阅读 1202
ZK
《从Paxos到Zookeeper 分布式一致性原理与实践》读书笔记
本文:总结脑图地址:脑图
所有的典型应用场景,都是利用了ZK的如下特性:
常见的应用就是配置中心,发布者将数据发布到ZooKeeper的一个或多个节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布订阅系统一般有两种设计模式:分别是推(Push)模式和拉(Pull)模式。在推模式下,服务端主动将数据更新发送给所有订阅的客户端;而拉模式则是由客户端主动发起请求来获取最新数据,通常客户端都采用定时进行轮询拉取的方式。
ZooKeeper采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发送变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知之后,需主动到服务端获取最新的数据。
通常,放到配置中心上的数据都具有如下特点:
1. 数据量通常比较小。
2. 数据内容在运行时会发生动态变化。
3. 集群中各机器共享,配置一致。
是指通过指定的名字来获取资源或者服务的地址,提供者的信息。利用Zookeeper很容易创建一个全局的路径,而这个路径就可以作为一个名字,它可以指向集群中的集群,提供的服务的地址,远程对象等。简单来说使用Zookeeper做命名服务就是用路径作为名字,路径上的数据就是其名字指向的实体。
阿里巴巴集团开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表。在Dubbo实现中:
服务提供者在启动的时候,向ZK上的指定节点/dubbo/${serviceName}/providers
目录下写入自己的URL地址,这个操作就完成了服务的发布。
服务消费者启动的时候,订阅/dubbo/{serviceName}/providers
目录下的提供者URL地址, 并向/dubbo/{serviceName} /consumers
目录下写入自己的URL地址。
注意,所有向ZK上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。另外,Dubbo还有针对服务粒度的监控,方法是订阅/dubbo/{serviceName}
目录下所有提供者和消费者的信息。
分布式定时任务:
心跳检测:
基于ZooKeeper的临时节点特性,可以让不同的机器都在ZooKeeper的一个指定节点下创建临时子节点,不同的机器之间可以根据这个临时节点来判断对应的客户端机器是否存活。通过这种方式,检测系统和被检测系统之间并不需要直接相关联,而是通过ZooKeeper上的某个节点进行关联,大大减少了系统耦合。
HA:同心跳检测。
使用ZK构建分布式锁都是利用了ZK能够保证高并发情况下,子节点创建的唯一性。
在特定目录下,创建临时子节点,创建成功,则表示获得锁。创建失败,直接返回加锁失败或者监听指定节点,然后等待加锁成功的节点释放锁后自己再尝试获取锁。
以加锁key名称创建永久节点,然后争抢锁的节点都在该永久节点下创建临时有序节点,序号最小的节点即获得锁。序号非最小的节点,监听前一个节点,而非全部节点,收到通知后,再验证自己是否是序号最小的节点,如果是,则表示获得锁,否则重新监听前一个节点。
所有尝试加锁的节点都在指定目录下创建临时顺序节点,形如/shared_lock/[hostname]-请求类型-序号
的临时顺序节点,例如/shared_lock/192.168.0.1-R-00000001
,就代表了一个共享锁;/shared_lock/192.168.0.1-W-00000001
,就代表是一个写锁。
根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。基于这个原则,我们来看看如何通过ZooKeeper的节点来确定分布式读写顺序:
/shared_lock
节点下的所有子节点,并确定自己的节点序号在所有子节点中的顺序。利用ZooKeeper的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法重复创建一个已经存在的数据节点。当多台机器同时争抢Master角色时,大家都往指定目录下创建临时节点,在这个过程中,只有一个客户端能够成功创建这个节点,那么这个客户端所在的机器就称为了Master。同时,没有创建成功的客户端都在该节点上注册一个监听事件,用于监控当前的Master机器是否存活,一旦发现当前的Master挂了,那么其余的客户端将会重新进行Master选举。
序号最小的先出队列:
getChildren()
接口来获取该节点下的所有子节点,即获取队列中所有的元素。分布式Barrier规定了一个队列的元素必须都集聚后才能统一进行安排,否则一直等待。这往往出现在那些大规模分布式并行计算的应用场景上:最终的合并计算需要基于很多并行计算的子结果来进行。 大致的设计思路如下:
开始时,/queue_barrier
节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数字n来代表Barrier值,例如n=10表示只有当/queue_barrier
节点下的子节点个数达到10后,才会打开Barrier。
所有客户端都会到/queue_barrier
节点下创建一个临时节点。
创建成功后,通过调用getData()
接口获取/queue_barrier
节点的数据内容:10。
getChildren()
接口获取/queue_barrier
节点下的所有子节点,即获取队列中的所有元素,同时注册对子节点列表变更的Watcher监听。对于涉及到顺序有序节点,排序等待的场景。不需要监听父节点或者其他全部兄弟节点,只要监听序号比自己小的第一个节点即可,这样可以防止接收到大量无用的Watcher
通知。