Zookeeper典型使用场景

数据发布订阅:

概述:

数据发布/订阅( Publish/Subscribe)系统,即所谓的配置中心,顾名思义就是发布者将数据发布到ZooKeeper的一个或一系列节点上,供订阅者进行数据订阅,
进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
两种设计模式:
推(Push)模式:
服务端主动将数据更新发送给所有订阅的客户端
拉(Pull)模式:
由客户端主动发起请求来获取最新数据,通常客户端都采用定时进行轮询拉取的方式。ZooKeeper采用的是推拉相结合的方式:
客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知之后,需要主动到服务端获取最新的数据。

案例:

需求:
我们就以一个”数据库切换”的应用场景展开,看看如何使用ZooKeeper来实现配置管理。
配置存储:
在进行配置管理之前,首先我们需要将初始化配置存储到ZooKeeper上去。一般情况下,我们可以在ZooKeeper上选取一个数据节点用于配置的存储,例如/appl/database_config
配置获取:
集群中每台机器在启动初始化阶段,会从上面提到的ZooKeeper配置节点上读取数据库信息,同时,客户端还需要在该配置节点上注册一个数据变更的Watcher监听, 一旦发生节点数据变更, 所有订阅的客户端都能够获取到数据变更通知。
配置变更:
在系统运行过程中,可能会出现需要进行数据库切换的情况,这个时候就需要进行配置变更。借助ZooKeeper,我们只需要对ZooKeeper上配置节点的内容进行更新,ZooKeeper就能够帮我们将数据变更的通知发送到各个客户端,每个客户端在接收到这个变更通知后,就可以重新进行最新数据的获取。

负载均衡:

负载均衡是一种相当常见的计算机网络技术,用来对多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源进行分配负载,以达到优化资掘使用、最大化吞吐率、最小化响应时间和避免过载的目的。通常负载均衡可以分为硬件和软件负载均衡两类。

动态DNS系统架构:

Zookeeper典型使用场景

• Register集群负责域名的动态注册。
• Dispatcher集群负责域名解析。
• Scanner集群负责检测以及维护服务状态(探测服务的可用性、屏蔽异常服务节点等)
• SDK提供各种语言的系统接人协议,提供服务注册以及查询接口。
• Monitor负责收集服务信息以及对DDNS自身状态的监控。
• Controller是一个后台管理的Console,负责授权管理、流量控制、静态配置服务和手动屏蔽服务等功能,另外,系统的运维人员也可以在上面管理Register 、
Dispatcher和Scanner等集群。

运行流程:

1)域名注册
域名注册主要是针对服务提供者来说的。域名注册过程可以简单地概括为: 每个服务提供者在启动的过程中,都会把自己的域名信息注册到Register集群中去。

1.服务提供者通过SDK提供的APl接口,将域名、IP 地址和端口发送给Register集群。例如,A机器用于提供serviceA.xxx. com,于是它就向Register发送一个“域名 → lP:PORT”的映射:”serviceA.xxx.corn → 192.168.0.1 :8080”
2.Register 获取到域名,IP 地址和端口配置后,根据域名将信息写入相对应的ZooKeeper域名节点中。

2)域名解析
域名解析是针对服务消费者来说的,服务消费者在使用域名的时候,会向Dispatcher发出域名解析请求。Dispatcher收到请求后,会从ZooKeeper上的指定域名节点读取相应的IP: PORT列表,通过一定的策略选取其中一个返回给前端应用。

3)域名探测
域名探测是指DDNS系统需要对域名下所有注册的IP地址和端口的可用性进行检测,俗称“健康度检测”。

健康皮检测一般有两种方式:
第一种是服务端主动发起健康度心跳检测,这种方式一般需要在服务端和客户端之间建立起一个TCP长链接
第二种则是客户端主动向服务端发起健康度心跳检测

在DDNS 架构中的域名探测,使用的是第二种健康度检测方式,即每个服务提供者都会定时向Scanner 汇报自己的状态。Scanner 会负责记录每个服务提供者最近一次的状态汇报时间, 一旦超过5 秒没有收到状态汇报,那么就认为该IP 地址和端口已经不可用,于是开始进行域名清理过程。在域名清理过程中, Scanner会在ZooKeeper 中找到该域名对应的域名节点,然后将该IP地址和端口配置从节点内容中移除。

命名服务:

概述:

命名服务也是分布式系统中比较常见的一类场景,在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等。最重要的的是要保证其名字全局唯一。说起全局唯一ID,大家联想到UUID没错,UUID是通用唯一识别码(Universally Unique Identifier)的简称。一个标准的UUID是一个包含32位字符和4个短线的字符串,但它有两个缺点:1长度过长 2含义不明

Zookeeper中生成全局唯一ID:

我们已经提到,通过调用ZooKeeper节点创建的APl接口可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字。利用这个特性,我们就可以借助ZooKeeper来生成全局唯一的ID了
生成步骤:
1.所有客户端都会根据自己的任务类型,在指定类型的任务下面通过调用create()接口来创建一个顺序节点,例如创建”job-“节点
2.节点创建完毕后create()接口会返回一个完整的节点名,例如”job-0000000003”
3.客户端拿到这个返回值后,拼接上type类型,例如”type2-job-0000000003”,这就可以作为一个全局唯一的ID了
Zookeeper典型使用场景

Master选举:

概述:

利用ZooKeeper的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无住重复创建一个已经存在的数据节点。也就是说,如果同时有多个客户端请求创建同一个节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行Master选举了。

具体实现:

首先会在ZooKeeper上创建一个日期节点,例如”2018-01-10”,客户端集群每天都会定时往ZooKeeper上创建一个临时节点,例如/master_election/2018-01-10/binding。在这个过程中,只有一个客户端能够成功创建这个节点,那么这个客户端所在的机器就成为了Master。同时,其他没有在ZooKeeper上成功创建节点的客户端,都会在节点/master_election/2013-01-10上注册一个子节点变更的Watcher,用于监控当前的Master机器是否存活,一旦发现当前的Master挂了,那么其余的客户端将会重新进行Master选举。

分布式锁:

概述:

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么出问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要使用分布式锁了。
在平时的实际项目开发中,我们往往很少会去在意分布式锁,而是依赖于关系型数据库固有的排他性来实现不同进程之间的互斥。这确实是一种非常简便且被广泛使用的分布式锁实现方式。然而有一个不争的事实是,目前绝大多数大型分布式系统的性能瓶颈都集中在数据库操作上。因此,如果上层业务再给数据库添加一些额外的锁,例如行锁、表锁甚至是繁重的事务处理,那么会让数据库更加不堪重负。

排它锁实现步骤:

1)定义锁:
通过在ZooKeeper上的数据节点来表示一个锁,例如/exclusive-lock/lock节点就可以被定义为一个锁
2)获取锁:
在需要获取排他锁时,所有的客户端都会试图通过调用create()接口,在/exclusive-lock节点下创建临时子节点/exclusive-lock/lock。ZooKeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。
3)释放锁:
在”定义锁”部分,我们已经提到/exclusive_lock/lock 是一个临时节点,因此在以下两种情况下,都有可能释放锁。
- 当前获取锁的客户端机器发生岩机,那么ZooKeeper上的这个临时节点就会被移除。
- 正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。
无论在什么情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive-lock节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布
式锁获取,即重复”获取锁”过程。整个排他锁的获取和释放流程,见下图。
Zookeeper典型使用场景

共享锁实现步骤:

轻量级(集群规模在10台以内):

1)定义锁:
通过在ZooKeeper上的数据节点来表示一个锁,是一个类似于
“/shared_lock/[Hostname]-请求类型-序号”的临时顺序节点,例如/shared_lock/192.168.0.1-R-0000000001,那么,这个节点就代表了一个共享锁。
2)获取锁:
在需要获取共享锁时,所有客户端都会到/shared_lock这个节点下面创建一个临时顺序节点,如果当前是读请求,那么就创建例如/shared_lock/192.168.0.l-R-0000000001的节
点;如果是写请求,那么就创建例如/shared_lock/192.168.0.1-W-0000000001的节点。
3)判断读写顺序:
根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。基于这个原则过ZooKeeper确定分布式读写顺序大致可以分为如下4个步骤:
A创建完节点后,获取/shared一lock 节点下的所有子节点,井对该节点注册子节点变更的Watcher监听。
B确定自己的节点序号在所有子节点中的顺序。
C对于读请求:
如果没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑。如果比自己序号小的子节点中有写请求,那么就需要进入等待。
对于写请求:
如果自己不是序号最小的子节点, 那么就需要进入等待。
D接收到Watcher通知后,重复步骤1。
3)释放锁:
释放锁的逻辑和排他锁是一致的。
总结:
上述方案实现简单,在集群规模不是特别大的情况下性能都还可以, 一般是在l O 台机器以内
在这整个分布式锁的竞争过程中,大量的”Watcher通知”和”子节点列表获取”两个操作重复运行,井且绝大多数的运行结果都是判断出自己并非是序号最小的节点,从而继续等待下一次通知。如果同一时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知——这就是所谓的羊群效应。

改进后的方案:

每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可
1.客户端调用create()方能创建一个类似于”/shared_lock/[Hostname]-请求类型”的临时顺序节点。
2.客户端调用getChildren()接口来获取所有已经创建的子节点列表,注意,这里不注册任何Watcher。
3.如果无法获取共享锁,那么就调用exist()来对比自己小的那个节点注册Watcher。
注意,这里“比自己小的节点”只是一个笼统的说法,具体对于读请求和写请求不一样。
读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。
写请求:向比自己序号小的最后一个节点住册Watcher监听。
4.等待Watcher通知,继续进入步骤2。
Zookeeper典型使用场景

Dubbo中的应用:

Zookeeper典型使用场景

• /dubbo:这是Dubbo在ZooKeeper上创建的根节点。
• /dubbolcom.foo.BarService:这是服务节点,代表了Dubbo的一个服务。
• /dubbo/com.foo.BarService少roviders:这是服务提供者的根节点,其子节点代表了每一个服务的真正提供者。
• /dubbo/com.foo.BarService/consumers:这是服务消费者的根节点,其子节点代表了每一个服务的真正消费者。
我们以”com.foo.BarService”这个服务为例,来说明Dubbo基于ZooKeeper实现的注册中心的工作流程。
服务提供者
服务提供者在初始化启动的时候,会首先在ZooKeeper的/dubbo/com.foo.BarService/providers节点下创建一个子节点,并写入自己的U肚地址,这就代表了”com.foo.
BarService”这个服务的一个提供者。
服务消费者
服务消费者会在启动的时候,读取并订阅ZooKeeper上/dubbo/com.foo.BarService/proνiders节点下的所有子节点,并解析出所有提供者的URL地址来作为该服务地址列表,然后开始发起正常调用。同时,服务消费者还会在ZooKeeper的/dubbo/com.foo.BarService/consumers节点下创建一个临时节点,井写入自己的URL地址,这就代表了”com.foo.BarService”这个服务的一个消费者。
监控中心
监控中心是Dubbo中服务治理体系的重要一部分,它需要知道一个服务的所有提供者和订阅者,及其变化情况。因此,监控中心在启动的时候,会通过ZooKeeper的/dubbo/com.foo.BarService节点来获取所有提供者和消费者的URL地址,井注册Watcher来监听其子节点变化。

注意:所有提供者在ZooKeeper上创建的节点都是临时节点,利用的是临时节点的生命周期和客户端会话相关的特性,因此一旦提供者所在的机器出现故障导致该提供者无法对外提供服务时,该临时节点就会自动从ZooKeeper上删除,这样服务的消费者和监控中心都能感知到服务提供者的变化。