概述
1.Zookeeper 的典型应用场景
Zookeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它来进行分布式数据的发布和订阅。
通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watcher 事件通知机制,可以非常方便的构建一系列分布式应用中年都会涉及的核心功能,如:
- 数据发布/订阅
- 负载均衡
- 命名服务
- 分布式协调/通知
- 集群管理
- Master 选举
- 分布式锁
- 分布式队列
1.1数据发布/订阅
发布者将数据发布到Zookeeper的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新
发布/订阅系统的两种设计模式:
- PUSH:服务端主动把数据更新发送给所有订阅者客户端
- PULL:客户端主动发起请求来获取最新数据,通常是定时轮训拉取方式
- Zookeeper采用推拉结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生更改,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端拉取最新的数据。
数据(配置信息)特性:
(1)数据量通常比较小
(2)数据内容在运行时会发生动态更新
(3)集群中各机器共享,配置一致
1.2 负载均衡
在分布式的环境中,我们常常使用集群部署的方式来提高某个服务的可用性,为了让高并发的请求能够平均的分配到集群中的每一个服务,避免有些服务压力过大,而有些服务处于空闲状态这样的情况,我们需要制定一些规则来把请求进行路由,这种分配请求的做法就叫做负载均衡,路由请求的规则就是负载均衡的策略。
1.2.1常见的负载均衡策略:
- Round Robin 轮询策略
- Random 随机策略
- Consistent Hashing 一致性哈希策略
- 加权随机策略
- 加权轮询策略
1.2.2 Zookeeper 实现负载均衡
- 服务注册:对于集群中的server(服务提供者)在启动过程中,把自身的地址信注册到zookeeper集群中(临时节点)
- 服务发现:针对服务消费者通过zookeeper来获得需要调用的服务名称节点下的机器列表节点。
- 服务调用:通过前面所介绍的负载均衡算法,选取其中一台服务器进行调用。
在这个过程中,服务消费者只有在第一次调用服务时需要查询zookeeper(服务配置中心),然后将查询到的服务信息缓存到本地,后面的调用直接使用本地缓存的服务地址列表信息,而不需要重新发起请求到服务配置中心去获取相应的服务地址列表,直到服务的地址列表有变更(机器上线或者下线),变更行为会触发服务消费者注册的相应的watcher进行服务地址的重新查询。这种无中心化的结构,使得服务消费者在服务信息没有变更时,几乎不依赖配置中心,解决了之前负载均衡设备所导致的单点故障的问题,并且大大降低了服务配置中心的压力。
通过Zookeeper来实现服务动态注册、机器上线与下线的动态感知,扩容方便,容错性好,且无中心化结构能够解决之前使用负载均衡设备所带来的单点故障问题。
ZooKeeper负载均衡和Nginx负载均衡区别:
ZooKeeper: 不存在单点问题,zab机制保证单点故障可重新选举一个leader只负责服务的注册与发现,不负责转发,减少一次数据交换(消费方与服务方直接通信),需要自己实现相应的负载均衡算法。
Nginx: 存在单点问题,单点负载高数据量大,需要通过 KeepAlived + LVS 备机实现高可用。每次负载,都充当一次中间人转发角色,增加网络负载量(消费方与服务方间接通信),自带负载均衡算法。
但是突然有一台服务器出现问题怎么办?
所以服务器提供负载均衡需要做到健康检测机制。
1.2.3健康检查
健康检查用于检查服务器开放的各种服务的可用状态。
例如创建一个HTTP健康检查,Get一个页面回来,并且检查页面内容是否包含一个指定的字符串,如果包含,则服务是可用的,如果不包含或者取不回页面,就认为该服务器的Web服务是不可用(DOWN)的。
创建健康检查时可以设定检查的间隔时间和尝试次数,例如设定间隔时间为5秒,尝试次数为3,那么负载均衡设备每隔5秒发起一次健康检查,如果检查失败,则尝试3次,如果3次都检查失败,则把该服务标记为DOWN,然后服务器仍然会每隔5秒对DOWN的服务器进行检查,当某个时刻发现该服务器健康检查又成功了,则把该服务器重新标记为UP。健康检查的间隔时间和尝试次数要根据综合情况来设置,原则是既不会对业务产生影响,又不会对负载均衡设备造成较大负担。
健康检测两种方式:
第一种:服务端主动发起心跳检测,这种方式需要服务端和客户端建立起一个TCP长链接;
第二种:客户端向服务端发起健康心跳检测。
1.3 命名服务
命名服务是指通过指定的名字来获取资源或者服务的地址,利用 zk 创建一个全局唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
1.4 分布式协调/通知
- 心跳检测:基于Zookeeper临时节点的特性,不同的机器在Zookeeper的一个指定节点下创建临时节点,不同机器根据临时节点判断客户端是否存活。通过这种方式:检测系统和被检测系统之间不需要直接关联,而是通过Zookeeper上某个节点进行关联,大大减少了系统耦合。
- 工作进度汇报:通过在一个节点下创建临时子节点实现两个功能:
- 判断任务机是否存活
- 各个任务机器实时的将自己的任务执行进度写到这个临时节点
- 系统调度:修改某些节点数据 ,以事件通知形式发送给对应的订阅端
1.5 集群管理
所谓集群管理无在乎两点:是否有机器退出和加入、选举 master。
根据Zookeeper两大特性:注册watcher事件通知 和 临时节点特性
对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper 的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。
新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount 又有了。
对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为 master 就好。
集群示例:
- 分布式日志收集系统:收集器机器,根据不同的业务日志区分,对应创建节点,每个节点存储日志源机器列表,如果收集器节点宕机,新增收集器节点。
1.6 分布式锁
1.6.1 分布式锁的特点
顾名思义,分布式锁就是实现在分布式网络环境中的锁。也就是说,在锁的基础上加上分布式的特性,我们来分析一下分布式锁实现的必要条件:
- 在分布式环境中,多个进程对资源的访问必须具有顺序性;
- 获取锁和释放锁的过程需要高可用和高性能;
- 具有锁失效的机制,避免死锁;
- 非阻塞的锁,没有获取到锁直接返回获取锁失败。
1.6.2 分布式锁的实现技术
Redis: 使用 setnx 命令来添加 key,key 添加成功说明当前无人使用此 key,也就是说无人使用此资源,相当于获取锁。再次使用 setnx 命令来添加相同的 key 时,此时 key 已存在就会添加失败,说明有人已经使用了这个 key,也就是说此资源被人占用,相当于获取锁失败;
Zookeeper: 使用 Zookeeper 临时顺序节点的特性,实现分布式锁和锁的等待队列。
1.6.3 分布式锁常用的类型
分布式锁常用的类型有两种:一种是排他锁,一种是共享锁。
排他锁
排他锁也叫独占锁,顾名思义,也就是对资源进行独占。排他锁只允许获取了该锁的线程,对具有排他锁的资源进行访问,无论是写操作还是读操作,直到该线程主动释放掉排他锁。
共享锁
共享锁也就是把资源进行共享,当然共享的只有读操作。共享锁只对写操作进行加锁,其它线程的读操作不做加锁操作,这样的共享机制提高了对资源访问的性能。
排他锁:X锁
1、定义锁:通过数据节点表示一个锁,例如:/zookeeper_lock/lock节点
2、获取锁:所有的客户端/zookeeper_lock/下创建临时子节点 /zookeeper_lock/lock。Zookeeper保证只有一个客户端能创建成功,为获取锁,同时,所有没有创建成功的客户端要在/zookeeper_lock节点注册一个子节点变更的Watcher监听。
3、释放锁:当客户端宕机或者执行完成后,主动将自己的创建的临时节点删除。Zookeeper会将子节点变更通知给所有注册了该节点Watcher的客户端,客户端收到通知后,再次重新发起分布式锁获取。
共享锁:S锁
- 定义锁:通过数据节点表示一个锁,例如:"/zookeeper_shared_lock/请求类型-序号"的临时顺序节点,如:/zookeeper_shared_lock/R-000000001。
- 获取锁:在需要获取共享锁时,在/zookeeper_shared_lock/下创建临时节点。
- 判断读写顺序:
3.1. 获取/shared_lock下的所有子节点,并对该节点注册子节点变更的Watcher监听
3.2.确定自己的节点序号顺序
3.3 分类逻辑:对于读请求:如果没有比自己更小的序号的子节点,或是所有比自己序号的小的子节点都是读请求,那么表明自己已经成功获取S锁,执行读逻辑,如果比自己序号小的请求中有写请求,那么等待对于写请求:如果自己不是序号最小的子节点,那么等待 。对于写请求:如果自己不是序号最小的子节点,那么等待。 - 接收到Watcher通知后,重复步骤1 4、释放锁:同上。
羊群效应:在释放锁时客户端会收到大量的事件通知,绝大多数序号不紧邻,也会收到通知,称为羊群效应
改进后的分布式锁实现:每个锁竞争者只需关注序号比自己小的那个节点。
1、客户端创建锁。
2、客户端获取已创建的子节点列表,但不注册Watcher。
3、如果无法获取锁,对比自己小的节点注册Watcher事件。
4、等待Watcher通知。
1.6.4 zooKeeper分布式锁的优点和缺点
(1)优点:ZooKeeper分布式锁(如InterProcessMutex),能有效的解决分布式问题,不可重入问题,使用起来也较为简单。
(2)缺点:ZooKeeper实现的分布式锁,性能并不太高。为啥呢?
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。大家知道,ZK中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器还需要将数据同不到所有的Follower机器上,这样频繁的网络通信,性能的短板是非常突出的。
总之,在高性能,高并发的场景下,不建议使用ZooKeeper的分布式锁。而由于ZooKeeper的高可用特性,所以在并发量不是太高的场景,推荐使用ZooKeeper的分布式锁。
1.7分布式队列
FIFO:先入先出,共享锁的数据节点
1、调用getChildren()获取所有子节点,即所有元素
2、确定自己的节点序号在所有子节点中的顺序
3、如果自己不是最小的序号,那么等待,同时向比自己序号小的最后一个节点注册Watcher监听
4、接收到Watcher通知后,重复步骤1
Barrier:分布式屏障
思想:/queue_barrier是一个存在的节点,并且该节点内容为10,数字类型n,表示:只有当/queue_barrier下的子节点个数达到10个,才会打开Barrier。
临时节点
1、通过getData()获取节点内容,n=10。
2、通过getChildren()接口获取/queue_barrier节点下的所有子节点,同时注册对子节点列表变更的Watcher。
3、统计子节点个数。
4、如果子节点个数<10,进入等待。
5、接收到Watcher通知,重复2步骤。
1.8注册中心
实现:依赖于临时节点
- 消费者启动的时候,会先去注册中心中全量拉取服务的注册列表。
- 当zk上某个服务节点有变化的时候,通过监听机制做数据更新。
- ZooKeeper挂了,由于消费者本地有保存 不服务提供者信息,不影响消费者的服务调用。
作为注册中心的缺点分析
- 数据一致性需求分析:ZK是CP系统,注册中心更需要AP系统,优先保证可用性,服务保证最终一致性即可。
- 分区容忍及可用性:ZK当发生网络分区或者宕机会导致影响分区内的服务的可用性;在实践中,注册中心不能因为自身的任何原因破坏服务之间本身的可连通性,这是注册中心设计应该遵循的铁律。
- 服务规模:服务规模增长,写请求会导致整个注册中心长连接的压力;ZooKeeper 的写并不是可扩展的,不可以通过加节点解决水平扩展性问题。
- 持久化需求:注册中心不一定需要持久存储和事务日志;在服务发现中,服务调用发起方更关注的是其要调用的服务的实时的地址列表和实时健康状态,每次发起调用时,并不关心要调用的服务的历史服务地址列表、过去的健康状态。
- 健康检测问题:zk的健康检测只是简单的TCP长链接活性探测,监控检测逻辑过于简单。
- 复杂性:难以掌握的Client/Session状态机;难以承受的异常处理。
最后
以上就是怕孤单黄豆为你收集整理的zookeeper场景篇的全部内容,希望文章能够帮你解决zookeeper场景篇所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复