Zookeeper1:相关理论

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zookeeper1:相关理论相关的知识,希望对你有一定的参考价值。

目录

1 为什么要有zookeeper

2 zk应用实例

3 分布式系统的问题

4. 分布式系统设计为什么这么难

5. ACID、CAP和BASE理论

6 Zookeeper关键设计

6.1 树形结构

6.2 监视与通知

6.3 znode带版本

7. 其他


 

1 为什么要有zookeeper

一句话:zookeeper是为了在分布式环境下,让开发人员可以实现通用的协作任务,包括选举主节点、管理组内成员关系、管理元数据等。

在大数据和云计算盛行的今天,应用服务由很多个独立的程序组成,这些独立的程序则运行在形形色色、千变万化的一组计算机上。相对于开发在一台计算机上运行的单个程序,如何让一个应用中多个独立的程序协同工作是一件非常困难的事情。开发这样的应用,很容易让很多开发人员陷入如何使多个程序协同工作的逻辑中,最后导致没有时间更好地思考和实现他们自己的应用程序逻辑,又或者开发人员对协同逻辑关注不够,只是用很少的时间开发了一个简单脆弱的主协调器,导致不可靠的单一失效点。

ZooKeeper的设计保证了其健壮性,这就使得应用开发人员可以更多关注应用本身的逻辑,而不是协同工作上。ZooKeeper从文件系统API 得到启发,提供一组简单的API,使得开发人员可以实现通用的协作任 务,包括选举主节点、管理组内成员关系、管理元数据等。

ZooKeeper的核心功能就一条:它可以在分布式系统中协作多个任务。一个协作任务是指一个包含多个进程的任务。这个任务可以是为了协作或者是为了管理竞争。协作意味着多个进程需要一同处理某些事情,一些进程采取某些行动使得其他进程可以继续工作。比如,在典型的主-从(master-worker)工作模式中,从节点处于空闲状态时会通知主节点可以接受工作,于是主节点就会分配任务给从节点。竞争则不同,它指的是两个进程不能同时处理工作的情况, 一个进程必须等待另一个进程。同样在主-从工作模式的例子中,我们 想有一个主节点,但是很多进程也许都想成为主节点,因此我们需要实现互斥排他锁(mutual exclusion)。实际上,我们可以认为获取主节点身份的过程其实就是获取锁的过程,获得主节点控制权锁的进程即主节点进程。

不过要注意的是,整个ZooKeeper的服务器集群管理着应用协作的关键数据,而不适合用作海量数据存储。对于需要存储海量的应用数据的情况,我们有很多其他方案,比如说数据库和分布式文件系统等。因为不同的应用有不同的需求,如对一致性和持久性的不同需求,所以在设计应用时,最佳实践还是应该将应用数据和协同数据独立开。

ZooKeeper中实现了一组核心操作,通过这些可以实现很多常见分布式应用的任务。你知道有多少应用服务采用主节点方式或进程响应跟踪方式,虽然ZooKeeper并没有为你实现这些任务,也没有为应用实现主节点选举,或者进程存活与否的跟踪的功能,但是,ZooKeeper提供了实现这些任务的工具,对于实现什么样的协同任务,由开发人员自己决定。

2 zk应用实例

zk是很多流行中间件的祖师爷,很多都用其实现协调、选举等功能,但是目前的趋势是zk正在逐步“寿终正寝”,很多软件为了降低复杂度都自行实现了zk的功能,例如分布式任务调度XXL-job就是使用了zk的elasticJob更方便好用,kafka3也放弃了zk。不过这不代表zk不好,而是kafka等为了降低自身的复杂度。使用了ZK的流行中间件有:

Apache HBase

HBase是一个通常与Hadoop一起使用的数据存储仓库。在HBase 中,ZooKeeper用于选举一个集群内的主节点,以便跟踪可用的服务 器,并保存集群的元数据。

Apache Kafka

Kafka是一个基于发布-订阅(pub-sub)模型的消息系统。其中ZooKeeper用于检测崩溃,实现主题(topic)的发现,并保持主题的生 产和消费状态。

Apache Solr

Solr是一个企业级的搜索平台。Solr的分布式版本命名为 SolrCloud,它使用ZooKeeper来存储集群的元数据,并协作更新这些元数据。

Yahoo!Fetching Service

Yahoo!Fetching Service是爬虫实现的一部分,通过缓存内容的方 式高效地获取网页信息,同时确保满足网页服务器的管理规则(比如 robots.txt文件)。该服务采用ZooKeeper实现主节点选举、崩溃检测和元数据存储。

Facebook Messages

Facebook推出的这个应用(http://on.fb.me/1a7uViK )集成了 email、短信、Facebook聊天和Facebook收件箱等通信通道。该应用将 ZooKeeper作为控制器,用来实现数据分片、故障恢复和服务发现等功能。

除此之外,还有Hadoop、阿里的Dubbo、Canal、Otter、JStorm等等。

3 分布式系统的问题

为什么会产生一个zk这样的软件呢?一定是为了满足某种特定需求的。虽然前面介绍了zk的作用,但是比较肤浅,我们还要结合分布式系统的特点和问题进一步阐明。

在《分布式系统概念与设计》一书中,对其定义是:分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。这个定义比较直白,一个分布式系统中的计算机可以部署在不同的机柜里,不同的机房里,甚至不同的城市、国家。无论怎么样,一个标准的分布式系统在没有任何特定业务逻辑约束的情况,都会有如下特征:

分布性

分布式系统中的机器随机分布,无法预判,而且还可能随时会变动。

对等性

虽然我们会指定一些主从策略和副本策略等,但是没有能够控制整个集群的主机。而且这些节点的低位是可以变化的,因此整体上仍然是对等的。

并发性

这个是基础要求,不同的结点都可以对外提供服务,可以并发的执行任务。这就涉及任务的划分、共享资源管理(例如数据库、缓存等)等一系列问题,因此如何准确高效地协调分布式并发操作也称为了分布式系统架构与设计中最大的挑战之一。

缺乏全局时钟

我们前面说典型的分布式系统中结点是通过交换消息来进行相互通信的,那此时就很难定义两个事件的先后顺序,原因就是因为分布式系统缺乏一个全局的时钟序列控制。

故障总会发生

前面说分布式系统中各个结点是平等的,还可以并发对外服务,那如果某个结点出现异常该怎么办呢?一个程序员都明白的道理是:设计阶段出现的问题,如果不处理一定会在线上再次遇到,而且解决的代价会越来越高,另外经常有很多设计阶段未考虑到的异常情况。所以分布式系统在设计之初就必须有底线思维:如果若干结点挂了,该如何保证整体稳定性,如何将危害降到最低。

4. 分布式系统设计为什么这么难

分布式系统设计为什么这么难?有的要用一个复杂的设计解决一个不可靠的问题,但是该复杂的结构又引入了新的不可靠,然后再设计更多,结果更不可靠。一个经典的问题就是"拜占庭将军问题",拜占庭将军问题(Byzantine Faults)是指可能导致一个组件发生任意行为(常常是意料之外的)的故障。这个故障的组件可能会破坏应用的状态,甚至是恶意行为。系统是建立在假设会发生这些故障,需要更高程度的复制并使用安全原语的基础上。尽管我们从学术文献中知道, 针对拜占庭将军问题技术发展已经取得了巨大进步,我们还是觉得没有必要在ZooKeeper中采用这些技术,因此,我们也避免代码库中引入额外的复杂性。 理想的情况下,我们基于异步通信的假设来设计系统,即我们使用的主机有可能发生时间偏移或通信故障。我们做出这个假设是因为这一 切的确会发生,时间偏移时常会发生,我们偶尔就会遇到网络问题,甚至更不幸的,发生故障。我们可以做什么样的限制呢? 我们来看一个最简单的情况。假设我们有一个分布式的配置信息发生了改变,这个配置信息简单到仅仅只有一个比特位(bit),一旦所有运行中的进程对配置位的值达成一致,我们应用中的进程就可以启动。 这个例子原本是一个在分布式计算领域非常著名的定律,被称为FLP(由其作者命名:Fischer,Lynch,Patterson),这个结论证明了在异步通信的分布式系统中,进程崩溃,所有进程可能无法在这个比特位的配置上达成一致。类似的定律称为CAP,表示一致性 (Consistency)、可用性(Availability)和分区容错性(Partition- tolerance),该定律指出,当设计一个分布式系统时,我们希望这三种属性全部满足,但没有系统可以同时满足这三种属性,因此ZooKeeper的设计尽可能满足一致性和可用性。 因此,我们无法拥有一个理想的故障容错的、分布式的、真实环境存在的系统来处理可能发生的所有问题。但我们还是可以争取一个稍微不那么宏伟的目标。首先,我们只好对我们的假设或目标适当放松,例如,我们可以假设时钟在某种范围内是同步的,我们也可以牺牲一些网络分区容错的能力并认为其一直是一致的,当一个进程运行中,也许多次因无法确定系统中的状态而被认为已经发生故障。虽然这些是一些折中方案,而这些折中方案允许我们建立一些印象非常深刻的分布式系统。

我们再看一个主从关系的例子,考虑在分布式系统设计中一个得到广泛应用的架构:一个主-从 (master-worker)架构。该系统中遵循这个架构的一个重要例子是HBase——一个Google的数据存储系统(BigTable)模型的实现,在最高层,主节点服务器(HMaster)负责跟踪区域服务器(HRegionServer)是否可用,并分派区域到服务器。我们重点一般的主-从架构,一般在这种架构中,主节点进程负责跟踪从节点状态和任务的有效性,并分配任务到从节点。对ZooKeeper来说,这个架构风格具有代表性,阐述了大多数流行的任务,如选举主节点,跟踪有效的从节点,维护应用元数据。 在这个系统,要实现主-从模式的系统,我们必须解决以下三个关键问题: 主节点崩溃 如果主节点发送错误并失效,系统将无法分配新的任务或重新分配已失败的任务。 从节点崩溃 如果从节点崩溃,已分配的任务将无法完成。 通信故障 如果主节点和从节点之间无法进行信息交换,从节点将无法得知新 任务分配给它。为了处理这些问题,之前的主节点出现问题时,系统需要可靠地选 举一个新的主节点,判断哪些从节点有效,并判定一个从节点的状态相 对于系统其他部分是否失效。我们将会在下文中介绍这些任务。

 为此我们也知道要设计一个良好的主从结构,至少应该解决如下几个问题:

主节点选举 这是关键的一步,使得主节点可以给从节点分配任务。 崩溃检测 主节点必须具有检测从节点崩溃或失去连接的能力。 组成员关系管理主节点必须具有知道哪一个从节点可以执行任务的能力。 元数据管理主节点和从节点必须具有通过某种可靠的方式来保存分配状态和执行状态的能力。

理想的方式是,以上每一个任务都需要通过原语的方式暴露给应用,也就是一个简单的接口调用就行了,对开发者完全隐藏实现细节。ZooKeeper提供了实现这些原语的关键机制,因此,开发者可以通过这些实现一个最适合他们需求、更加关注应用逻辑的分布式应用。

5. ACID、CAP和BASE理论

为了能够更准确的描述上面提到的这些问题,学术界提出了一些概念,例如ACID、CAP和BASE等等。 前面提到了分布式系统可能存在的各种问题,但是还有一个问题也很重要,那就是发生改变时,系统整体是稳定的,但是在处理过程中出现少量数据丢失、处理错误、重复处理等问题,这个问题可以统一称为“分布式事务处理与数据一致性”问题。虽然受影响的用户并一定很多,但是中招的用户会非常不爽,如果处理的是钱等核心数据, 引发的后果会更加严重,因此必须认真对待。

ACID

事务是由一系列对系统中的数据进行访问与更新的操作所组成的一个执行单元,这个主要是针对数据的。事务可以在这些应用程序之间提供一个隔离方法,防止彼此的操作相互干扰,并且提供了从失败中恢复到正常状态的方法,还提供了数据库即使在异常情况下也能保持数据一致性的方法。

事务具有四个特征:分别是原子性Atomicity、一致性Consistency、隔离性Isolation和持久性Durability,合在一起就是ACID特征。而隔离性又读未授权、授权读取、可重复度和串行化几个级别。有这个不再赘述,详细请参考数据库相关内容。

在单机模式下,mysql等本身就能解决这些问题 ,我们只要设置一下就好了,那分布式环境下数据和操作都是分散在不同的机器上的,那该如何处理呢?

CAP理论

在分布式环境下是很难实现两全其美的方案的。例如高可用意味着处理速度快,而不得不放弃一些强验证。而强一致性又会占用更多的资源导致处理速度变慢,因此只能做平衡,设计一个兼顾高可用和一致性的方案。其中最经典的就是CAP理论和BASE理论。

2000年开始CAP理论逐步成为分布式计算领域公认的定理,其核心是一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availabliity)和分区容错性(Partition tolerance)这三个基本需求,最多只能满足两个。在分布式环境下,一致性是指多个副本之间能够保持一致。可用性是系统提供的服务必须一致处于可用的状态,这要求时间要有限并且结果要正确。分区容错性是系统在遇到任何网络分区故障的时候,仍然需要保证对外提供满足一致性和可用性的服务。

BASE理论

BASE 是Basically Available(基本可用)、soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写。是对CAP中一致性和可用性权衡的结果,核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式来使得系统达到最终一致性。这里包含三个要素:

基本可用

基本可用是指分布式系统在出现不可预知的故障时,允许损失部分可用性,但是系统本身仍然基本可用的,例如响应时间正常是0.5秒,而现在变成了5秒。或者不太重要的功能暂时不可用,例如节日大促的时候,用户评价和积分不可用,而下单、支付等都是正常的。

软状态

软状态也叫弱状态,是指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统的整体稳定性,即允许系统在不同节点的数据副本之间进行数据同步的过程中存在时延,例如用户评,现在已经有一万个五星,但是数据在分散节点上,没有完全同步过来,只展示有1000个五星好评。

最终一致性

最终一致性是系统中所有的数据副本在经过一段时间的同步之后,最终达到一个一致状态,因此最终状态的本质是需要系统保证最终数据能够达到一致,而不需要实时保证数据的强一致性。例如我们说的五星好评的例子,在大促结束之后,系统整体的压力降下来了,就可以重新汇总计算所有商品的好评情况,将所有的数据改对。

在应用中,还根据上述几种情况产生了几个变种,例如因果一致性、读已之所写、会话一致性、单调一致性等等。事实上用的时候还会结合几种来设计一个更完备的方案。例如缓存、基于消息队列的削峰填谷等等、空闲时间安排定时任务等等都是类似的思想。

6 Zookeeper关键设计

介绍了这么多,终于能介绍Zookeeper是怎么工作的了。我们介绍一下Zookeeper里的关键设计:

6.1 树形结构

整体来说Zookeeper就是一个树形结构,通过这个树可以实现很多高级的功能。树中每一个节点都是一个znode。下图描述了一个znode树的结构,根节点包含4个子节点,其中三个子节点拥有下一级节点,叶子节点存储了数据信息。

 针对一个znode,没有数据常常表达了重要的信息。比如,在主-从模式的例子中,主节点的znode没有数据,表示当前还没有选举出主节点。而上图中涉及的一些其他znode节点在主-从模式的配置中非常有用:

·/workers节点作为父节点,其下每个znode子节点保存了系统中一个可用从节点信息。如上图所示,有一个从节点(foot.com:2181)。

·/tasks节点作为父节点,其下每个znode子节点保存了所有已经创 建并等待从节点执行的任务的信息,主-从模式的应用的客户端在/tasks 下添加一个znode子节点,用来表示一个新任务,并等待任务状态的znode节点。

·/assign节点作为父节点,其下每个znode子节点保存了分配到某个从节点的一个任务信息,当主节点为某个从节点分配了一个任务,就会 在/assign下增加一个子节点。

Zookeeper提供了几个基础的api用来管理树中的结点:

  • create/path data 创建一个名为/path的znode节点,并包含数据data。

  • delete/path删除名为/path的znode。

  • exists/path检查是否存在名为/path的节点。

  • setData/path data 设置名为/path的znode的数据为data。

  • getData/path 返回名为/path节点的数据信息。

  • getChildren/path 返回所有/path节点的所有子节点列表。

需要注意的是,ZooKeeper并不允许局部写入或读取znode节点的数据。当设置一个znode节点的数据或读取时,znode节点的内容会被整个替换或全部读取进来。

当新建znode时,还需要指定该节点的类型(mode),不同的类型决定了znode节点的行为方式。

持久节点和临时节点

znode节点可以是持久(persistent)节点,还可以是临时 (ephemeral)节点。持久的znode,如/path,只能通过调用delete来进行删除。临时的znode与之相反,当创建该节点的客户端崩溃或关闭了与 ZooKeeper的连接时,这个节点就会被删除。

持久znode是一种非常有用的znode,可以通过持久类型的znode为应用保存一些数据,即使znode的创建者不再属于应用系统时,数据也可以保存下来而不丢失。例如,在主-从模式例子中,需要保存从节点的任务分配情况,即使分配任务的主节点已经崩溃了。

临时znode传达了应用某些方面的信息,仅当创建者的会话有效时 这些信息必须有效保存。例如,在主从模式的例子中,当主节点创建的znode为临时节点时,该节点的存在意味着现在有一个主节点,且主节点状态处于正常运行中。如果主znode消失后,该znode节点仍然存在,那么系统将无法监测到主节点崩溃。这样就可以阻止系统继续进行,因此这个znode需要和主节点一起消失。我们也在从节点中使用临时的znode,如果一个从节点失效,那么会话将会过期,之后znode/workers也将自动消失。

一个临时znode,在以下两种情况下将会被删除:

1.当创建该znode的客户端的会话因超时或主动关闭而中止时。

2.当某个客户端(不一定是创建者)主动删除该节点时。

因为临时的znode在其创建者的会话过期时被删除,所以我们现在 不允许临时节点拥有子节点。在社区讨论中,已经讨论过关于允许临时znode拥有子节点的问题,其想法是使其子节点也均为临时节点。这个功能也许会出现在未来的发布版本中,但现在还是不可用的。

有序节点

一个znode还可以设置为有序(sequential)节点。一个有序znode节点被分配唯一个单调递增的整数。当创建有序节点时,一个序号会被追加到路径之后。例如,如果一个客户端创建了一个有序znode节点,其路径为/tasks/task-,那么ZooKeeper将会分配一个序号,如1,并将这个数字追加到路径之后,最后该znode节点为/tasks/task-1。有序znode通过提供了创建具有唯一名称的znode的简单方式。同时也通过这种方式可以直观地查看znode的创建顺序。

总之,znode一共有4种类型:持久的(persistent)、临时的 (ephemeral)、持久有序的(persistent_sequential)和临时有序的 (ephemeral_sequential)。

6.2 监视与通知

ZooKeeper通常以远程服务的方式被访问,如果每次访问znode时,客户端都需要获得节点中的内容,这样的代价就非常大。因为这样会导致更高的延迟,而且ZooKeeper需要做更多的操作。考虑图2-2中的例子,第二次调用getChildren/tasks返回了相同的值,一个空的集合,其实是没有必要的。下图演示了同一个结点多次读取的例子。

 这是一个常见的轮询问题。为了替换客户端的轮询,我们选择了基于通知(notification)的机制:客户端向ZooKeeper注册需要接收通知的znode,通过对znode设置监视点(watch)来接收通知。监视点是一个单 次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。在图2-3阐述的情况下,当节点/tasks发生变化时,客户端会收到一个通知,并从ZooKeeper读取一 个新值。

  当使用通知机制时,还有一些需要知道的事情。因为通知机制是单次触发的操作,所以在客户端接收一个znode变更通知并设置新的监视点时,znode节点也许发生了新的变化(不要担心,你不会错过状态的变化)。让我们看一个例子来说明它到底是怎么工作的。假设事件按以下顺序发生:

1.客户端c1 设置监视点来监控/tasks数据的变化。

2.客户端c1 连接后,向/tasks中添加了一个新的任务。

3.客户端c1 接收通知。

4.客户端c1 设置新的监视点,在设置完成前,第三个客户端c3 连接后,向/tasks中添加了一个新的任务。

客户端c1最终设置了新的监视点,但由c3添加数据的变更并没有触发一个通知。为了观察这个变更,在设置新的监视点前,c1实际上需要读取节点/tasks的状态,通过在设置监视点前读取ZooKeeper的状态,最终,c1就不会错过任何变更。 通知机制的一个重要保障是,对同一个znode的操作,先向客户端传送通知,然后再对该节点进行变更。如果客户端对一个znode设置了监视点,而该znode发生了两个连续更新。第一次更新后,客户端在观察第二次变化前就接收到了通知,然后读取znode中的数据。我们认为主要特性在于通知机制阻止了客户端所观察的更新顺序。虽然ZooKeeper的状态变化传播给某些客户端时更慢,但我们保障客户端以全局的顺序来观察ZooKeeper的状态。 ZooKeeper可以定义不同类型的通知,这依赖于设置监视点对应的通知类型。客户端可以设置多种监视点,如监控znode的数据变化、监控znode子节点的变化、监控znode的创建或删除。为了设置监视点,可以使用任何API中的调用来读取ZooKeeper的状态,在调用这些API时,传入一个watcher对象或使用默认的watcher。

思考:谁来管理我的缓存 如果不让客户端来管理其拥有的ZooKeeper数据的缓存,我们不得不让ZooKeeper来管理这些应用程序的缓存。但是,这样会导致ZooKeeper的设计更加复杂。事实上,如果让ZooKeeper管理缓存失效,可能会导致ZooKeeper在运行时,停滞在等待客户端确认一个缓存失效的请求上,因为在进行所有的写操作前,需要确认所有的缓存数据是否已经失效。

6.3 znode带版本

每一个znode都有一个版本号,它随着每次数据变化而自增。两个API操作可以有条件地执行:setData和delete。这两个调用以版本号作为转入参数,只有当转入参数的版本号与服务器上的版本号一致时调用才会成功。当多个ZooKeeper客户端对同一个znode进行操作时,版本的使用就会显得尤为重要。例如,假设客户端c1 对znode/config写入了一些 配置信息,如果另一个客户端c2 同时更新了这个znode,此时c1 的版本号已经过期,c1调用setData一定不会成功。使用版本机制有效避免了以上情况。在这个例子中,c1在写入数据时使用的版本无法匹配,使得操作失败。下图使用版本来阻止并行操作的不一致性的情况。

7. 其他

zk的安装还是比较简单的,在mac下可以使用brew install直接来安装,比较简单,这里不再赘述。对结点进行增删改查等网上也能找到很多,这里不再赘述。

Zookeeper部署集群也比较简单,不过在初学阶段没必要部署集群, 这会增加学习的复杂性,可以在对其有深入理解之后再进行。

在终端执行“zkServer start”,开启zookeeper服务。使用zkCli进入客户端。

关于Zookeeper的学习材料比较少,目前只有两个:《从Paxos到Zookeeper  分布式一致性原理与实践》,里面废话比较多,而且很多内容都过时了。稍微新一些的只有《ZooKeeper-分布式过程协同技术详解》,这本书整体介绍地还是不错的,但是翻译的水平实在不敢恭维。

喜欢我,就关注我吧!

以上是关于Zookeeper1:相关理论的主要内容,如果未能解决你的问题,请参考以下文章

朴素贝叶斯分类-理论篇

统计学习基本理论知识

SpringBoot--服务注册发现实战

分布式文件系统和企业级应用——zookeeper集群和kafka的相关概念就部署

Java连接Zookeeper,创建监听通知机制

Java连接Zookeeper,创建监听通知机制