JavaEE 企业级分布式高级架构师(八)Zookeeper学习笔记(3)

Zookeeper典型应用场景

为进一步加强对 zk 的认识,理解 zk 的作用,下面详细介绍一下 zk 在生产环境中的典型应用场景。

配置维护

什么是配置维护

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

数据发布/订阅

  • 发布/订阅模式是一对多的关系,多个订阅者对象同时监听某一主题对象,这个主题对象在自身状态发生变化时会通知所有的订阅者对象,使它们能自动的更新自己的状态。发布/订阅可以使得发布方和订阅方独立封装、独立改变。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时可以使用发布/订阅模式。发布/订阅模式在分布式系统中的典型应用有配置管理服务发现、注册
  • 参考资料:https://www.cnblogs.com/sky-sql/p/6685531.html

实现原理

在这里插入图片描述

  • zk可以通过“发布/订阅模型”实现对集群配置文件的管理和维护。“发布/订阅模型”分为推模式(Push)与拉模式(Pull)。zk的“发布/订阅模型”采用的是推拉相结合的模式。
  • 首先每一个集群客户端需要向 zk 注册一个某数据节点的 watcher 监听,当发布者将更新过的配置数据发布到 zk 后,就会触发各个 watcher 监听,即 zk 会向每一个订阅者送 watcher 事件,订阅者接收到 watcher 事件后,就会从 zk 中取更新过的配置数据。Zookeeper具有同步操作的原子性,可以确保每个集群主机的配置信息都能被正确的更新。

命名服务

什么是命名服务

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

实现原理

在这里插入图片描述

  • 通过利用 zk 中顺序节点自动生成唯一编号的特定来实现命名服务。
  • 首先创建一组业务相关的节点,然后再在这些节点下再创建顺序节点,此时的顺序节点的路径+名称即为生成的唯一标识。

集群管理

需求

对于集群,我们总是希望能够随时获取到以下信息:

  • 当前集群中各个主机的运行时状态是怎样的;
  • 及时了解到集群中主机的上下线状况;
    zk 可以提供集群主机存活性监控功能。

基本原理

在这里插入图片描述
在 zk 中创建一个监控系统节点,例如 /clusterManager,然后将集群主机均注册为其临时子节点。然后,再在 /clusterManager 上注册一个 watcher 监听。一旦集群中添加或删除主机,就会触发子节点列表数量变化的 watcher 事件。当然,至于监控程序捕获到的 watcher 事件如何处理,那就要由监控程序的业务来定义了。

分布式日志收集系统

下面以分布式日志收集系统为例来分析 zk 对于集群的管理

系统组成

在这里插入图片描述
首先,需要清除,分布式日志收集系统由三部分组成:日志源集群、日志收集器集群和zk集群。

  • 日志源集群:日志都是由该集群中的主机生成。其会由于扩容、宕机、网络等问题引发集群中主机数量的变化。
  • 日志收集器集群:用于收集日志源集群中主机生成的日志。一个日志收集器要收集基于日志源集群中的日志数据,其也会由于扩容、宕机、网络等问题引发集群主机数量的变化,从而引发收集任务的再分配。
  • zk集群:用于注册日志收集器。其自身主机数量的变化不是这里要研究的内容。
收集器的注册

在这里插入图片描述

  • 在 zk 创建一个节点作为收集器的根节点,例如 /logs/collector,然后,每个收集器在启动时都会在该节点下创建自己的节点,例如 /logs/collector/[host]。这些节点是临时节点还是持久节点呢?需要根据后面的分析确定。
任务分配
  • 等所有收集器都注册完毕后,系统根据收集器的个数,将所有日志源集群主机分组后,以数据内容的形式分别写入到各个收集器节点。各个收集器就可以按照自己的日志源主机列表进行日志收集工作。
状态收集
  • 这里的状态收集指的是两方面的收集:一个是收集器收集其所管理的日志源主机状态,一个是日志收集器的运行状态。
  • 每个收集器会定期从自己所管理的日志源主机上读取日志,若在指定时间内读取不到日志信息,则认为该日志源主机宕机;在指定时间内可以读取到的日志源主机则被认为该日志源主机是健康的。这些是否健康的信息会作为数据内容定时写入到收集器数据内容中。
  • 当然,当前收集器主机的运行状态参数(开发者自己定义,例如已经收集了多少子节的日志、当前CPU、内存的使用情况等)也会定时以数据内容的形式写入到收集器数据内容中。
任务再分配

当出现收集器挂掉或扩容,就需要动态进行日志收集任务再分配了。日志系统会在 /logs/collector 中注册一个 watcher,随时关注其子节点数量的变化。当子节点数量发生变化时,进行任务再分配。通常分配方案有两种:

  • 全局动态分配:将所有日志源主机全部进行统一再分配。该方案实现起来简单,但对日志系统的性能会有较大的影响。
  • 局部动态分配:首先到定义各个收集器的负载判别标准,这个判别标准是由开发者自行定义的。可以根据每台收集器所分配的日志源主机数量判别,也可以根据收集器主机的硬件条件与日志源主机数量等参数综合判别。若出现收集器挂掉,则根据各个收集器的负载情况,将挂掉的收集器的原任务分配给负载小的一个或若干个收集器;若收集器扩容,则将负载较大的一个或若干个收集器的部分日志源主机分配给新增的收集器。

DNS服务

zk 的 DNS 服务是命名服务的一种特殊用法,其对外表现出的功能主要是防止提供者的单点问题,实现对提供者的负载均衡。
在这里插入图片描述

什么是DNS

  • DNS(Domain Name System:域名系统),即可以将一个名称与特殊的主机IP+端口号进行绑定。zk 可以充当 DNS 的作用,完成域名到主机的映射。
  • 不过需要注意的是,“域名”并不一定必须是类似于“xxx.com”形式的字符串,而可以是任意形式的字符串序列。
  • 例如,在分布式系统中,一些工程是专门为其它工程提供服务的,这些工程称为“服务提供者”;而一些工程是专门调用其它工程所提供的服务的,这些工程称为“服务消费者”。为了使消费者与提供者间接解耦,消费者一般都是通过“服务名称”来消费服务的。那么,将“服务名称”与“服务提供者”间建立映射关系的服务器称为“服务注册中心”。这个服务注册中心所起的作用就是 DNS 的作用——将服务名称映射为提供者主机。zk 就可以充当服务注册中心。

基本DNS实现原理

在这里插入图片描述

  • 假设应用程序 app1 与 app2 分别用于提供 service1 与 service2两种服务,现在要将其注册到 zk 中。首先需要在 zk 上创建一组节点进行域名配置,例如 /DNS/app1/service1.company.com 与 /DNS/app2/service2.company.com。即为每一个应用程序创建一个节点,然后再以该应用的服务名称为为名称再创建子节点,而子节点的数据内容则为该应用程序所部属的服务器主机的“主机名+端口号”,若有多个主机上均部属着同一个应用,即该服务的提供者有多个,那么这些“主机名+端口号”间用逗号隔开(当然,也可以使用其它分隔方式)。
  • 对于该结构的用法,需要注意一下几点:
* 若该应用具有多个服务名称,则可以在该应用节点下添加多个子节点。
* 若某域名下的服务者增加或减少,可直接修改该域名对应节点的数据内容即可。
* 若某域名需要修改,可直接修改该域名对应节点的名称或再新增一个节点。
* 对于这些节点及节点数据内容的使用,例如多个提供者可以实现负载均衡,都是由程序员编写的 zk 客户端程序自己定义的,即这种客户端代码需要开发人员自行开发。

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

  • 具有状态收集功能的DNS的zk节点示意图:
    在这里插入图片描述
  • 以上模型存在一个问题,如何获取各个提供者主机的健康状态、运行状态呢?可以为每一个域名节点再添加一个状态子节点,而该节点的数据内容则为开发人员定义好的状态数据。这些状态数据是如何h获取到的呢?是通过状态收集器(开发人员自行开发的)定期写入到 zk 的该节点中的。
  • 阿里的 Dubbo 就是使用 Zookeeper 作为域名服务器的。

扩展-集群监控平台

  • 集群监控平台示意图:
    在这里插入图片描述

优化设计

  • 前面的设计存在一个问题:当提供者主机宕机了,对于状态聚合系统没办法实时感知到,而且设计比较复杂。
  • 改进后的方案如下:
    在这里插入图片描述

Master选举

什么是Master选举

  • 集群是分布式系统中不可或却的组成部分,是为了解决分布式系统中计算单元的单点问题,水平扩展计算单元的处理能力的一种解决方案。
  • 一般情况下,会在群集中选举出一个 Master,用于协调集群中的其它 Slave 主机,对于Slave 主机的状态具有决定权。

广告推荐系统

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

在这里插入图片描述

Master选举
  • 使用 DBMS 的主键唯一特性可以实现 Master 的选举。让所有集群主机向数据库某表中插入主键相同的记录,由于 DBMS 具有主键冲突检查功能,所以其只能有一个主机插入成功,那么这个成功的主机即为 Master,其它为 Slave。
  • 其存在的弊端是,仅使用 DBMS 的功能无法实现当 Master 宕机后对于 Slave 的通知,通知它们进行重新选举。

这个广告推荐系统集群中的 Master 是如何选举出来的呢?

  • 使用 zk 可以完成。使用 zk 中多个客户端对同一节点创建时,只有一个客户端可以成功的特性实现。

在这里插入图片描述
具体来说,由三步完成:

  • step1:多个客户端同时发起对同一临时节点 /master-election/master 进行创建的请求,最终只能有一个客户端成功。这个成功的客户端主机就是 Master,其它客户端就是 Slave。
  • Step2:让 Slave 都向这个临时节点的父节点 /master-election 注册一个子节点列表的watcher 监听。
  • Step3:一旦该 Master 宕机,临时节点就会消失,zk 服务器就会向所有 Slave 发送子节点变更事件,Slave 在接收到事件后会调用相应的回调方法,该回调方法会重新向这个父节点创建相应的临时子节点。谁创建成功,谁就是新的 Master。

分布式同步

什么是分布式同步

  • 分布式同步,也称为分布式协调,是分布式系统中不可缺少的环节,是将不同的分布式组件有机结合起来的关键。对于一个在多台机器上运行的应用而言,通常需要一个协调者来控制整个系统的运行流程,例如执行的先后顺序,或执行与不执行等。
  • 引入一个协调者,便于将分布式协调的职责从应用中分离出来,从而大大减少了系统间的耦合度,显著提高系统的可扩展性。

实现原理

使用 zk 实现分布式同步的原理是:不同的客户端都对 zk 上的同一个 znode 节点进行 watcher 监听,监听数据节点本身或其子节点的变化情况。根据不同的变化,客户端收到不同的 watcher 事件通知,做出不同的处理。

MySQL数据复制总线

下面以“MySQL数据复制总线”为例来分析 zk 的分布式同步服务。

数据复制总线组成
  • MySQL数据复制总线是一个实时数据复制框架,用于在不同的 MySQL 数据库实例间进行异步数据复制。其核心部分由三部分组成:生产者、复制管道、消费者。
    在这里插入图片描述
  • 复制管道重要功能由两部分构成:复制任务完成者 replicatorMySQL元数据提供者 metadataServer
    在这里插入图片描述
  • replicator:实现了数据复制的核心逻辑,从生产者(确切地说是MySQL的Binlog日志)中复制出数据写入到消费者。
  • metadataServer:存放复制任务所对应的生产者、消费者的元数据,例如数据库名、表名,连接数据的用户名、密码等信息。
  • 那么,MySQL数据复制总线系统中哪里需要使用 zk 的分布式同步功能呢?这需要了解复制总线系统的整个工作原理。下面就详细解析其工作过程与原理。
数据复制总线工作原理

在这里插入图片描述

复制任务注册
  • 在数据复制总线系统启动时,首先需要在 zk 中创建相应的任务列表节点,例如:/mysql_replicator/tasks。
  • 当 replicator 线程启动,说明要有复制任务了,其会在任务列表下创建一个任务子节点,例如要完成“支付记录”复制,则创建/mysql_replicator/tasks/pay_record子节点。若发现该子节点已经存在,则说明已经在replicator注册了任务,自己就不需要再创建该子节点了。
replicator热备
  • 为了防止 replicator 在复制过程中出现故障,replicator 采用热备容灾方案,即将同一个复制任务部署到多个不同的主机上,但又使一个处于 RUNNING 状态,而其他的主机处于 STANDBY 状态。当 RUNNING 状态的主机出现故障,无法完成复制任务时,式某一个 STANDBY 状态主机转换为 RUNNING 状态,继续完成复制任务。
  • 为了实现上述热备方案,会在相应任务节点下创建多个 replicator 主节点,这些节点属于临时顺序节点。其节点名称形式如下: /mysql_replicatir/tasks/pay_record/instances/[hostname]-序号,例如:/mysql_replicatir/tasks/pay_record/instances/host1-0000000001。
  • 在完成该任务下所有 replicator 主机对应的子节点创建后,每个任务节点(如pay_record)均可获取到该任务的所有 replicator 主机列表,然后将让序号最小的 replicator 状态设置为 RUNNING,其它的设置为 STANDBY。当然,这些状态信息会写入到当前任务节点下的 status 子节点的数据内容中,即写入到 /mysql_replicatir/tasks/pay_record/status 中。
主备切换
  • 当 RUNNING 态的主机出现宕机,则该主机对应的子节点马上就被删除了,然后在当前处于 STANDBY 状态中的 replicator 中找到序号最小的子节点,然后将其状态马上修改为RUNNING,完成“主备切换”。

分布式锁

排他锁

  • 排他锁(eXclusive locks),也称为 X 锁、写锁。若事务 T 对数据对象 O 加了排他锁,那么在整个加锁期间,只允许事务 T 对 O 进行读写操作,其他任何事务都不能对该数据对象进行任何操作,直到 T 释放了排他锁。

共享锁

  • 共享锁(Shared locks):又称为 S 锁、读锁。若事务 T 对数据对象 O 加了共享锁,那么该事务只能对 O 进行读操作。当然,其他事务同时也可以对数据对象 O 再添加共享锁,且可以对 O 进行读操作。只有当所有共享锁全部释放后,才可以对 O 添加排他锁,即才可以对 O 进行写操作。

分布式锁的实现

在 zk 上对于分布式锁的实现,使用的是类似于“/xs_lock/[hostname]-请求类型-序号”的临时顺序节点。
在这里插入图片描述
具体实现过程如下:

  • 首先客户端会对 xs_lock 节点注册子节点列表变更事件的 watcher 事件。
  • 然后,若客户端需要获取分布式锁时,会到 xs_lock 节点下创建一个临时顺序节点。若当前是读请求,则会创建一个 “hostname-R-序号”的子节点;若当前是写请求,则会创建一个“hostname-W-序号”的子节点。读写操作的顺序性就是通过这些子节点的顺序性体现的。
  • 在当前子节点创建完后,当前子节点会对比其它子节点序号的大小关系,并根据读写操作的不同,执行不同的逻辑。
    读请求:若没有比自己序号小的子节点,或所有比自己序号小的子节点都是读请求,则表明自己可以获取到共享锁,即可以开始数据读取了;若比自己序号小的子节点中有写请求,则当前客户端无法获取到共享锁,不能对数据进行读操作,而是进入等待状态,等待前面写操作的完成。
    写请求:若发现自己是序号最小的子节点,则表明当前客户端可以获取到排他锁,即可以开始数据更新了;若发现还有比自己序号更小的子节点,则当前客户端无法获取到排他锁,不能对数据进行写操作,而是进入到等待状态,等待前面的操作完成。
  • 存在的问题:前面每一个节点的变化,都会导致后面所有节点 watcher 事件的回调(下载子节点列表),这样就会很影响性能。

分布式锁的改进

  • 前面的实现方式存在惊群效应【由于一个操作而引发了大量的低效或无用的操作的执行,这种情况称为惊群效应。】,为了解决其所带来的性能下降,可以对前述分布式锁的实现进行改进。
  • 在客户端请求发出后,在 zk 中创建相应的临时顺序节点后马上获取当前的 /xs_lock 的所有子节点列表,但任何客户端都不向 /xs_lock 注册 watcher,而是改根据请求类型的不同向“对其有影响的”子节点注册watcher。
    读请求:若直接获取到共享锁,则直接开始读操作;若不能获取到共享锁,则说明其前面有写请求,其只需要向序号小于自己的最后一个写请求节点注册 watcher,然后等待。
    写请求:若查看到自己的序号是最小的节点,则直接获取到排他锁,然后开始写操作;若其不是序号最小的节点,则其只需向序号小于自己的最后一个节点注册 watcher,然后等待。

分布式队列

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

FIFO队列

在这里插入图片描述

  • zk实现FIFO队列的思路是:利用顺序节点的有序性,为每个数据在 zk 中都创建一个相应的节点。然后为每个节点都注册 watcher 监听。一个节点被消费,则会引发消费者消费下一个节点,直到消费完毕。
    具体实现过程如下:
  • 为每一个数据按照其到达的顺序为其创建顺序子节点,且将数据作为节点的数据内容。
  • 每个子节点在创建时,若其不是序号最小的节点,则需要再向小于其序号的最后一个节点中注册一个 watcher 监听
  • 当应用程序对一个节点的数据内容消费过后,马上修改该节点的数据内容。当后面节点监听到其前面节点的数据内容发生了改变,则表示当前节点的数据可以被消费了。

分布式屏障Barrier队列

  • Barrier,屏障、障碍物。Barrier队列是分布式系统中的一种同步协调器,规定了一个队列中的元素必须全部聚集齐后才能继续执行后面的任务,否则一直等待。其常见于大规模分布式并行计算的应用场景中:最终的合并计算需要基于很多并行计算的子结果来进行。
    在这里插入图片描述
    zk对于Barrier的实现原理是:
  • 首先要创建一个 /barrier 节点,其数据内容设置为屏障打开的阈值,即当其下的子节点数量达到该阈值后,app才可进行最终的计算,否则一直等待。
  • 然后应用程序再在 /barrier 中注册一个 watcher 监听,监听其下的子节点数量变化
  • 然后就可以开始每一个并行计算了。对于每一个并行计算,每计算出一个子结果,就会在 /barrier 下创建一个子节点,而每增加一个节点,/barrier 就会向app发送一个子节点数量变化的 watcher 事件,通知应用程序获取 /barrier 子节点列表,以便获取子节点个数。
  • 应用程序在获取到子节点个数后,会马上对比其与阈值的大小。当子节点个数达到阈值时,会开启最终的合并计算,即打开了屏障。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
开课吧-javaEE企业级分布式高级架构师是一门专注于培养企业级应用开发的高级技术课程。该课程旨在帮助学员全面掌握Java EE企业级开发的技能和知识,培养他们成为具备分布式应用系统设计和架构能力的高级架构师。 在这门课程中,学员将学习Java EE的核心概念和技术,包括Servlet、JSP、JDBC、EJB、JNDI等。同时,学员还将深入学习分布式应用开发的相关技术,如Web服务、消息队列、分布式缓存、负载均衡等。除此之外,课程还将涉及如何使用流行的Java EE开发框架(如Spring、Hibernate等)进行企业应用开发,并介绍分布式系统的设计原则和最佳实践。 通过学习这门课程,学员将能够了解分布式应用架构的基本原理,并具备设计和构建分布式应用系统的能力。他们将熟练掌握Java EE平台的各种技术和工具,能够灵活运用它们开发高性能、可扩展性强的企业级应用系统。此外,通过课程中的实战项目,学员还将锻炼解决实际问题和项目管理的能力。 作为一门高级架构师的课程,它将帮助学员进一步提升自己的职业发展。毕业后,学员可以在企业中担任分布式应用的架构师、系统设计师、技术经理等角色,负责企业级应用系统的设计和开发。此外,他们还可以选择独立开发,提供技术咨询和解决方案。 总之,开课吧-javaEE企业级分布式高级架构师是一门非常有价值的课程,它将帮助学员掌握Java EE企业级开发的核心技术和分布式应用架构的设计原理,培养他们成为具备高级架构师能力的软件开发专业人士。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值