Zookeeper典型应用场景

1,655 阅读20分钟

配置维护

什么是配置维护

​ 分布式系统中,很多服务都是部署在集群中的,即多台服务器中部署着完全相同的应用,起着完全相同的作用。当然,集群中的这些服务器的配置文件是完全相同的。

​ 若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件。如果我们集群服务器比较少,那么这些修改还不是太麻烦,但如果集群服务器特别多,比如某些大型互联网公司的 Hadoop 集群有数千台服务器,那么纯手工的更改这些配置文件几乎就是一件不可能完成的任务。即使使用大量人力进行修改可行,但过多的人员参与,出错的概率大大提升,对于集群所形成的危险是很大的。

实现原理

截屏2020-11-01 下午7.17.54

zk 可以通过“发布/订阅模型”实现对集群配置文件的管理与维护。“发布/订阅模型”分为推模式(Push)与拉模式(Pull)。zk 的“发布/订阅模型”采用的是推拉相结合的模式。

  • 发布者应用程序作为 zk 客户端首先需要在 zk 中创建一个节点。该节点的数据内容为当前被监控集群主机的配置文件。
  • 被监控集群主机在启动时首先需要从 zk 节点上读取数据内容,即配置文件内容。
  • 读取过数据内容后,集群主机会再向 zk 的该节点注册数据内容变更的 watcher监听。
  • 发布者将更新过的配置文件内容更新到 zk 对应节点的数据内容上。此时就会引发相应的 watcher 事件,然后 zk 会向每一台被监控主机推送 watcher 事件。
  • 被监控集群主机在接收到 watcher 事件后,会触发本地 watcher 调用执行其回调方法,回调方法会从 zk 中拉取节点的数据内容,即更新过的配置文件。

命名服务

什么是命名服务

命名服务是指可以为一定范围内的元素命名一个唯一标识,以与其它元素进行区分。在分布式系统中被命名的实体可以是集群中的主机、服务地址等。UUID,GUID

实现原理

截屏2020-11-01 下午8.31.42

通过利用 zk 中节点路径不可重复的特点来实现命名服务的。当然,也可以配带上顺序节点的有序性来体现唯一标识的顺序性。

  • 唯一标识生成器在启动时首先需要在 zk 中创建一个根节点,例如/app
  • 当需要一个唯一标识时,用户可以指定要生成的业务一级模块名称、二级模块名称。。。。然后在/app 下创建一个以一级模块名称命名的子节点,例如/app/order
  • 生成器再在该模块节点下再创建一个顺序节点,此时该顺序节点的路径即为生成的唯一标识

DSN服务

zk 的 DNS 服务的功能主要是实现消费者与提供者的解耦合,防止提供者的单点问题,实现对提供者的负载均衡。

截屏2020-11-01 下午8.40.54

什么是DNS

DNS,Domain Name System,域名系统,即可以将一个名称与特定的主机 IP 加端口号进行绑定。zk 可以充当 DNS 的作用,完成域名到主机的映射。

基本 DNS 实现原理

假设提供者应用程序 app1 与 app2 分别用于提供 service1 与 service2 两种服务,现要将其注册到 zk 中,具体的实现步骤如下图所示。

截屏2020-11-01 下午8.42.43
  • 在 zk 上为每一个提供者应用创建一个节点,例如/DNS/app1
  • 以该提供者的服务名称为名在对应的提供者应用节点下创建子节点,该节点即为域名节点,例如/DNS/app1/service1。
  • 为域名节点添加数据内容,数据内容为当前服务的所有提供者主机地址集合,即多个提供者地址间使用逗号分隔。

对于该结构需要注意以下几点:

  1. 若该应用具有多个服务名称,则可以在该应用下添加多个子节点。
  2. 若某域名下的服务提供者主机数据有变化,则直接修改该域名节点中的数据内容即可。
  3. 若需要修改某服务名称,可直接在该应用下添加一个新的节点,该节点名称为新的域名。
  4. 对于提供者的负载均衡,zk 是无法实现的,需要消费者将其要调用的服务提供者主机列表下载到本地后,自己编写代码实现。

具有状态收集功能的 DNS 实现原理

截屏2020-11-01 下午8.44.48

以上模型存在一个问题,如何获取各个提供者主机的健康状态、运行状态呢?可以为每一个域名节点再添加一个状态子节点,而该状态子节点的数据内容则为开发人员定义好的状态数据。这些状态数据是如何获取到的呢?是通过状态收集器(开发人员自行开发的)定期写入到 zk 的该节点中的。

阿里的 Dubbo 就是使用 Zookeeper 作为域名服务器的。

实战:广告推荐系统

需求

系统会根据用户画像,将用户归结为不同的种类。系统会为不同种类的用户推荐不同的广告。每个用户前端需要从广告推荐系统中获取到不同的广告 ID。

分析

这个向前端提供服务的广告推荐系统一定是一个集群,这样可以更加快速高效的为前端进行响应。需要注意,推荐系统对于广告 ID 的计算是一个相对复杂且消耗 CPU 等资源的过程。如果让集群中每一台主机都可以执行这个计算逻辑的话,那么势必会形成资源浪费,且降低了响应效率。此时,可以只让其中的一台主机去处理计算逻辑,然后将计算的结果写入到某中间存储系统中,并通知集群中的其它主机从该中间存储系统中共享该计算结果。那么,这个运行计算逻辑的主机就是 Master,而其它主机则为 Slave。

架构

截屏2020-11-01 下午8.53.52
  1. 用户发起请求到服务集群获取该用户的广告。此时直接访问存储中间件系统。

  2. 如果存储系统里面没有该类用户的广告,那么就去由Master计算出该类用户的广告,存储到DB/DFS然后再返回给客户端

Master选举

​ 使用 DBMS 的主键唯一特性可以实现 Master 的选举。让所有集群主机向数据库某表中插入主键相同的记录,由于 DBMS 具有主键冲突检查功能,所以其只能有一个主机插入成功,那么这个成功的主机即为 Master,其它为 Slave。

​ 其存在的弊端是,仅使用 DBMS 的功能无法实现当 Master 宕机后对于 Slave 的通知,通知它们进行重新选举。

截屏2020-11-01 下午8.55.26

具体实现步骤:

  1. 多个客户端同时发起对同一临时节点进行创建的请求,最终只有一个可以成功。该成功者即为 Master,其它为 Slave。
  2. 让 Slave 都向该临时节点的父节点注册一个子节点列表变更的 watcher 监听。
  3. 一旦该 Master 宕机,临时节点即会消失。zk 就会引发子节点列表变更事件,该事件会触发各个 Slave 的 watcher 回调的执行。而该回调则会重新发起向 zk 注册临时节点的请求,注册成功的即为新的 Master。

分布式锁

什么是分布式锁

分布式锁是控制分布式系统同步访问共享资源的一种方式。Zookeeper 可以实现分布式锁功能。根据用户操作类型的不同,可以分为排他锁与共享锁。

  • 共享锁(又称为重入锁):写锁。只在一个资源上没有添加排他锁,那么就可以在其上添加多把共享锁。
  • 排它锁(又称为公平锁):读锁。一个资源一旦添加了排他锁,那么就不能再添加其他锁了。当然,若一个资源已经添加了其它锁,那么,其是不能再添加排他锁的,直到该资源上的所有其他锁全部释放。

分布式锁的实现

​ 在 zk 上对于分布式锁的实现,使用的是类似于“/xs_lock/[hostname]-请求类型-序号”的临时顺序节点。当客户端发出读写请求时会在 zk 中创建不同的节点。根据读写操作的不同及当前节点与之前节点的序号关系来执行不同的逻辑。

截屏2020-11-01 下午9.36.20

实现步骤:

  1. 当一个客户端向某资源发出读/写请求时,若发现其为第一个请求,则首先会在 zk中创建一个根节点。若节点已经存在,则无需创建。
  2. 根节点已经存在了,客户端在根节点上注册子节点列表变更的 watcher 监听。
  3. watcher 注册完毕后,其会在根节点下人创建一个读/写操作的临时顺序节点。
  4. 节点创建完毕后,其就会马上触发客户端的 watcher 回调的执行。回调方法首先会将子节点列表读取,然后会查看序号比自己小的节点,并根据读写操作的不同,执行不同的逻辑。
    • 读请求:若发现没有比自己序号小的节点,或所有比自己序号小的节点都是读请求节点,则表明自己可以开始读取数据了。若发现比自己小的节点中存在写请求节点,则当前读请求等待,直到前面所有写请求执行完毕。
    • 写请求:若发现自己是序号最小的节点,则表明当前客户端可以开始写操作了。若发现自己不是最小的节点,则写操作等待,直到其前面所有操作全部执行完毕。
  5. 客户端读写操作完毕,其与 zk 的连接断开,则 zk 中该会话对应的节点消失。

分布式锁的改进

​ 前面的实现方式存在“羊群效应”,为了解决其所带来的性能下降,可以对前述分布式锁的实现进行改进。

​ 由于一个操作而引发了大量的低效或无用的操作的执行,这种情况称为羊群效应。

​ 当客户端请求发出后,在 zk 中创建相应的临时顺序节点后马上获取当前的/xs_lock 的所有子节点列表,但任何客户端都不向/xs_lock 注册用于监听子节点列表变化的 watcher。而是改为根据请求类型的不同向“对其有影响的”子节点注册 watcher。

  • 读请求:向序号比自己小的最后一个写请求节点添加 watcher 监听。

  • 写请求:向序号比自己小的最后一个节点添加 watcher 监听,无论是读还是写节点,其只关注比自己小的最后一个节点。

    注意:关键点是回调逻辑是什么?

分布式队列

​ 说到分布式队列,我们马上可以想到 RabbitMQ、Kafka 等分布式消息队列中间件产品。zk 也可以实现简单的消息队列。

FIFO队列

截屏2020-11-01 下午9.43.25

zk 实现 FIFO 队列的思路是:利用顺序节点的有序性,为每个数据在 zk 中都创建一个相应的节点。然后为每个节点都注册 watcher 监听。一个节点被消费,则会引发消费者消费下一个节点,直到消费完毕。

具体实现步骤:

  • 为每个数据按照其到达的顺序为其创建一个顺序子节点,其数据内容为数据本身。节点类型可以为持久顺序节点,也可以为临时顺序节点。不同类型不同监听方案。
  • 若节点类型为持久顺序节点。若其消费的是第一个节点,无需注册监听,可直接消费。若其消费的不是第一个节点,则需要向其前一个节点注册数据内容变更的watcher监听。当消watcher 回调的执行。回调内容为消费当前节点的数据,并在消费完毕后再修改数据内容。该修改会触发下一个消费者的回调执行。。。。直到消费完毕所有数据。费者对一个数据消费过后,其会马上修改该节点的数据内容。其会触发下一个消费者的
  • 若节点类型为临时顺序节点。若其消费的是第一个节点,无需注册监听,可直接消费。若其消费的不是第一个节点,则需要向其前一个节点注册节点删除事件的 watvher 监听。当消费者消费过后,直接将连接断开,连接断开会使该节点消失。节点消失会触发下一个节点的 watcher 回调。回调内容为消费当前节点数据。消费完毕,断开连接,断开连接则点消失。节点消失,则会触发下一个节点的 watcher 回调。。。。直到所有数据全部消费完毕。

分布式屏障 Barrier 队列

截屏2020-11-01 下午9.49.46

​ Barrier,屏障、障碍物。Barrier 队列是分布式系统中的一种同步协调器,规定了一个队列中的元素必须全部聚齐后才能继续执行后面的任务,否则一直等待。其常见于大规模分布式并行计算的应用场景中:最终的合并计算需要基于很多并行计算的子结果来进行。

​ zk 对于 Barrier 的实现原理是,在 zk 中创建一个/barrier 节点,其数据内容设置为屏障打开的阈值,即当其下的子节点数量达到该阈值后,app 才可进行最终的计算,否则一直等待。每一个并行运算完成,都会在/barrier 下创建一个子节点,直到所有并行运算完成。

​ 具体实现步骤:

  • 创建一个根节点,其数据内容为开始屏障的阈值
  • 应用程序向根节点注册一个子节点列表变更的 watcher 监听,并将根节点的数据内容读取出来。
  • 每一个并行计算的完成,都会在根节点下创建一个子节点。该节点的增加会触发应用程序 watcher 的回调执行。用于判断当前的子节点数据与阈值的大小。当子节点数据小于阈值时,应用程序等待。当它们相等时,应用程序开始最终运算,即打开屏障。

总结

  • 【Q-01】对于zk功能的开发,或者说是对于zk在具体应用场景中的解决方案的设计中,我们要着重考虑对哪两个zk特性的灵活使用?请谈一下你的看法。

    答:

  • Znode:

    节点是zk的核心功能特效之一,我们使用zk来实现自己的业务的时候就是基于znode节点来实现数据存储的。它的节点类似于Linux文件系统,一个节点下可挂载子节点,每个节点上都能保存数据。

  • Watcher:

    watcher机制实现了发布/订阅模式。watcher有以下几个特效

  1. **一次性:**一旦一个 watcher 被触发,zk 就会将其从客户端的 WatcherManager 中删除,服务端中也会删除该 watcher。zk 的 watcher 机制不适合监听变化非常频繁的场景。
  2. **串行性:**对同一个节点的相同事件类型的 watcher 回调方法的执行是串行的。
  3. **轻量级:**真正传递给 Server 的是一个简易版的 watcher。回调逻辑存放在客户端,没有在服务端。
  • 【Q-02】对于zk的节点类型,谈一下你的认识。

    答:

  • 持久节点

    1. 一旦创建后,将永久的存储到磁盘上。
    2. 即使断开会话也不会丢失节点数据。
    3. 当服务被重启时,会加载持久节点。
  • 持久顺序节点

    1. 同上三点一样
    2. 节点是有序的,节点名称上会有10个0,来依次标识节点被创建时的大小。例:zk0000000001,zk0000000002,zk0000000003,zk000000000........n。
  • 临时节点

    1. 节点被创建后,会话结束时/服务重启时。节点将被自动删除。
  • 临时顺序节点

    1. 同上一点一样

    2. 同持久顺序节点第二点一样

  • 【Q-03】查看Zookeeper的节点属性,哪个属性表示节点的类型?请谈一下你的认识。

    答:

​ ephemeralOwner属性标识节点类型:

  • 节点是持久节点值为0。

  • 临时节点值为当前会话的sessionid,会话结束后会根据sessionid来查找临时节点来对节点进行删除。

  • 【Q-04】对于zk 来说watcher机制非常重要,watcher机制的工作原理是怎样的?谈一下你的认识。

    答:

  • 生成watcher对象,存放到WatchManager

  • 向Server注册watcher监听

  • 发送watcher事件

  • 向Clinet发送相应事件通知

  • 根据通知从WatcherManager中找到对应的watcher对象

  • watcher对象执行回调

  • 【Q-05】对于zk 官方给出了四种最典型的应用场景,配置维护就是之一。什么是配置维护?请谈一下你的看法。 答:

​ 在大型分布式系统中,有成百上千集群/服务节点,当某一些服务配置发生变更的时候人工手动修改是不可取的,此时就应该用到分布式配置中心。市面上目前比较流行的有,携程的阿波罗,springcloud的config。

​ 当然,zk也可以使用znode节点和watcher监听机制(发布、订阅模式)来实现分布式配置中心,首先被监控的集群在zk中注册一个节点,并监听该节点的数据内容变更。当客户端发送节点数据发生变化的时候就会触发watcher监听机制来通知对应的服务端,服务端收到warcher回调后就去该节点读取内容。

  • 【Q-06】对于zk 官方给出了四种最典型的应用场景,命名服务就是之一。不过,像UUID、GUID等就可以非常方便地生成几乎不会重复的id,为什么还要那么麻烦的使用zk实现呢?

    答:

​ 这个只能说是根据不同的业务场景,来各取所需罢了。UUID,GUID确实是几乎不会发生重复,但是它们是无规则的,在我们程序员眼里它们就是一串几乎唯一的乱码代码。但是zk的节点可以自定义业务名称,如:order_0000000001

  • 【Q-07】对于分布式日志系统,无论是日志源主机还是日志收集主机,都需要监控其存活状态。请设计一种方案,可以方便地监控日志收集主机的存活状态。 答:

​ 就是在主机下再创建一个zk节点,存储状态数据,在根节点上注册一个子节点列表变更 watcher监听。当集群主机数量发生变化时,zk会向监控系统推送 watcher事情,然后监控系统执行主机对应的状态变更业务逻辑。

  • 【Q-08】使用DBMS可以实现Master选举,实现原理是什么?存在什么问题?请谈一下你的看法。

    答:

​ **原理:**利用主键,或者唯一索引,谁先写入数据成功,谁就当master。

​ **存在的问题:**性能差。

​ **看法:**选举的时候肯定是不能使用DBMS的,因为性能是在太差了,系统的瓶颈一般都在于DB。我们应该合理的用好程序中间件,各司其职,互不干涉。可以 使用加锁,zk节点等实现。

​ **存在的问题:**如果选出来的Master挂了,DB没有通知其他Salve发起选举的能力。

  • 【Q-09】使用zk可以实现Master选举,实现原理是什么?请谈一下你的看法。

    答:

​ zk实现选举的原理就是抢注,当Master挂了之后,所有的Salve都会去抢着当Master。此时(抢注)就是所有的Salve都去zk注册节点,最新注册的就当Master。

  • 【Q-10】“数据复制总线”的功能可以由MySQL主备集群完成吗?数据复制总线与zk有什么关系。请谈一下你的认识。

    答:

  • 在Mysql里面主从的数据复制是考binlog来完成的,这个binlong也是仅仅只能完成Mysql与Mysql直接的数据复制。

  • 而数据复制总线是可以复制不同数据库直接的数据的。

  • 与zk的关系是,zk能解决replicator的单点问题,replicator采用的是主备模式,意思就是只有一个replicator是工作状态。一个是就绪状态,另外一个就是备用状态。当就绪状态的这个replicator挂了之后,就会触发watcher监听机制,把备用状态的replicator改为就绪状态。

  • 【Q-11】我们可以通过上层业务向关系型数据库中添加额外的行锁、表锁或事务处理等来实现分布式锁。为什么还需要通过 zk 来实现分布式锁功能?请谈一下你 的看法。

    答:

表锁,行锁等诗单点业务的锁。而zk的分布式锁锁的是分布式服务节点上的并发操作。

  • 【Q-12】什么是锁机制?锁都有哪些分类?请谈一下你的认识。

    答:

读读并行,写写阻塞,读写阻塞,写读阻塞。

共享读(可重入锁)

排他写(公平锁)

  • 【Q-13】zk 可以实现分布式锁,Redis 也可以实现。由它们实现的分布式锁有什么区别?请谈一下你的认识。

答:

zk是使用它的临时节点添加删除监听机制来锁的,锁的生命周期是临时会话结束,临时节点自动删除。

redis是在缓存里面存储一个指定的key来实现分布式锁的,锁的生命周期是设置缓存的过期时间。

从性能上来说,zk的节点操作是肯定没有redis的内存操作性能那么高的。

  • 【Q-14】什么是 Barrier 队列?请谈一下你的认识

答:

在大型系统中,执行计算任务的实时由于单线程任务执行时间太久了,会将一个大任务拆分成很多个小任务。但是任务的最后业务是要拿到这100个线程计算的结果,才能执行最终的业务。所以有以下解决方案:

假设:现在我将一个大任务拆分成100个小任务去执行,每执行完一个就往zk中写入一个节点。然后定义wetcher机制去监听父节点下的子节点数量变更。当数量达到100的时候,就去读取这100个子节点的计算结果,然后再去执行那个最终任务。