ZooKeeper 是什么
简单一句话回答 ZooKeeper 是什么:ZooKeeper 是一个典型的分布式一致性的解决方案,分布式应用程序可以基于她实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁、分布式队列等功能。
ZooKeeper 可以保证如下分布式一致性特性:
顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到 ZooKeeper 中去。 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,另外一部分没有应用的情况。 单一视图 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 实时性: ZooKeeper 仅仅保证在一定的时间段内,客户端最终一定能够从服务器上读取到最新的数据状态。
咱们先来看一下分布式系统的一致性问题
从 ACID 到 CAP/BASE
在单机数据库中,我们很容易能实现 一 套满足 ACID 特性的事务处理系统,但在分布式数据库中,数据分散在各台不同的机器上,如何对这些数据进行分布式的事务处理具有非常大的挑战。分布式环境中会碰到种种问题,包括机器宕机、网络异常等等,但是为了保证分布式应用的可靠性,分布式事务是无法回避的问题。
对于一个高访问量、高并发的互联网分布式系统来说,实现一套严格满足 ACID 特性的分布式事务,很可能出现系统的可用性和严格一致性之间的冲突,因为当我们要求分布式系统严格一致性时,很可能牺牲掉系统的可用性。
因此,在可用性和一致性之间永远无法存在一个两全其美的方案,于是如何构建一个兼顾可用性和一致性的分布式系统成为无数工程师探讨的难题, 出现了 CAP 和 BASE 这样的分布式系统经典理论。
ACID
**事务(Transaction)**是有一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元,狭义上的事务特指数据库事务。一方面,当多个应用程序并发访问数据库时,事务可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作相互干扰。另一方面,事务为数据库操作序列提供一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持数据一致性的方法。
事务具有四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),简称ACID
Atomicity(原子性)
一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
Consistency(一致性)
**在事务开始之前和事务结束以后,数据库的完整性和一致性没有被破坏。**这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。也就是说,数据执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态,因此当数据库只包含成功事务提交的结果时,就能说数据库处于一致性状态。而如果数据库系统在运行过程中发生故障,有些事务尚未完成就中断,这些未完成的事务对数据库所做的修改有一部分已写入屋里数据库,这时数据库就处于一种不正确的状态,或者不一致的状态。
Isolation(隔离性)
**数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。**事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
隔离级别 | 脏读-Dirty Read | 不可重复读-NonRepeatableRead | 幻读-PhantomRead |
---|---|---|---|
未提交读-Read uncommitted | 可能 | 可能 | 可能 |
已提交读-Readcommitted | 不可能 | 可能 | 可能 |
可重复读-RepeatableRead | 不可能 | 不可能 | 可能 |
可串行化-Serializable | 不可能 | 不可能 | 不可能 |
脏读
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
不可重复读
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻读
第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

未提交读
如果一个事务正在处理某一数据,并对齐进行更新,并未提交事务,与此同时,允许另外一个事务也能够访问该数据。举例:事务 A 和事务 B 同时进行,事务 A 在整个执行阶段,会将某数据项的值从 1 开始,做一系列加法操作,直到变成 10 之后进行事务提交;事务 B,能够看到这个数据项在事务 A 操作过程中的所有中间值,这一系列中间值的读取就是未提交读。
已提交读
与未提交读的差别是,已提交读只允许获取已经被提交的数据。同样上面的例子:事务 B 无法看到这个数据项在事务 A 操作过程中的所有中间值,只能看到最终的 10;如果还有个事务 C,和事务 A 进行类似操作,将数据项从 10 加到 20,此时事务 B 同样可以读到 20。
可重复读
保证再事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的。因此该事务避免了不可重复读和脏读,但是还有可能幻读。在上面的例子中:可重复读级别可以保证事务 B 在一次事务操作过程中,始终对数据项读取到 1。
串行化
串行化是最严格的事务隔离级别。它要求所有事务都被串行执行,即事务只能一个接一个地处理,不能并发执行。
Durability(持久性)
**事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。**换句话说,一旦某个事务成功结束,那么它对数据库所做的更新就必须被永久的保存下来,即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束时的状态。
CAP
CAP原则又称CAP定理,** Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者的缩写。一个分布式系统中三者不能共存,最多只能满足三项中的两项。**

咱们先看一个简单的分布式系统。系统由两个 server 组成,G1 和 G2,两个服务同时存有变量 v 的值,G1 和 G2 之间可以通信,并且都能与客户端 client 进行通信,如下图所示,此时 v 的值是 v0。

client 可以发送读和写请求到任意一个 server,无论哪个 server 获得了请求都会做出相应的处理并回应。client的读写流程如下:
写请求:

读请求:

分区容错性(P)
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况。

上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
一致性(C)
一致性,即 client 访问分布式系统中的任何一个节点,在同一时刻获取变量的值是相同的。(等同于所有节点访问同一份最新的数据副本)举例来说,变量 v 的初始值 v0,client向 G1 发起一个写操作,将其改为 v1。对于非一致性系统,client 从 G2 获取得到的值是 v0

对于一致性系统,client 从 G2 获取则获取得到的值是 v1

可用性(A)
可用性,就是说**只要收到用户的请求,服务器就必须给出回应。**用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
Consistency 和 Availability 的矛盾
一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性。如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
对于一个分布式系统而言,分区容错性可以说是一个最基本的要求。为什么呢?因为既然是一个分布式系统,那么系统中的组件必定部署到不同的节点,否则也就无所谓分布式系统了。因此必定出现子网络,而且,对于不同的子网之间,网络通信问题是一定会出现的(例如网络延迟、网络抖动等),因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。所以,分布式系统架构设计的时候,往往需要把精力放在如何根据业务特点在 Consistency 和 Avaiability 之间寻求平衡。
BASE
BASE 理论是 **Basically Available (基本可用),Soft State(软状态)和 Eventually Consistent(最终一致性)**三个短语的缩写。
其核心思想是:既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
基本可用(Basically Available)
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性,但请注意,这绝不等价于系统不可用。以下是“基本可用”的典型例子。
响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎可以在 2 秒作用返回结果。 功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单。但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
软状态(Soft State)
什么是软状态呢?相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
最终一致性(Eventually Consistent)
**最终一致性,强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。**这个时间期限取决于网络延时、系统负载、数据复制方案设计等等因素。所以最终一致性本质是需要系统保证最终数据能够达到一致,而不是实时都保证系统数据的强一致性。
而在实际工程实践中,最终一致性存在以下五类主要变种:
因果一致性(Causal consistency):因果一致性指的是:如果节点 A 在更新完某个数据后通知了节点 B ,那么节点 B 之后对该数据的访问和修改都是基于 A 更新后的值。于此同时,和节点 A 无因果关系的节点 C 的数据访问则没有这样的限制。 读己之所写(Read your writes):读己之所写指的是:节点 A 更新一个数据后,它自身总是能访问到自身更新过的最新值,而不会看到旧值。其实也算一种因果一致性。 会话一致性(Session consistency):会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现 “读己之所写” 的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。 单调读一致性(Monotonic read consistency):单调读一致性指的是:如果一个节点从系统中读取出一个数据项的某个值后,那么系统对于该节点后续的任何数据访问都不应该返回更旧的值。 单调写一致性(Monotonic write consistency):单调写一致性指的是:一个系统要能够保证来自同一个节点的写操作被顺序的执行。
在实际的实践中,这五种变种往往会结合使用,以构建一个具有最终一致性的分布式系统。实际上,不只是分布式系统使用最终一致性,关系型数据库在某个功能上,也是使用最终一致性的。比如异步备份,数据库的复制过程是需要时间的,这个复制过程中,业务读取到的值就是旧的。当然,最终还是达成了数据一致性。这也算是一个最终一致性的经典案例。
总的来说,BASE 理论面向的是大型高可用可扩展的分布式系统,和传统事务的 ACID 特性是相反的,她完全不同于 ACID 的强一致性模型,而是提出通过牺牲强一致性来获的可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
ZooKeeper 的由来
《从 Paxos 到 ZooKeeper 》第四章第一节有段 ZooKeeper 由来的介绍
ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了。
ZooKeeper 概览
ZooKeeper 是一个开源的分布式协调服务,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
原语:操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。
ZooKeeper 是 Google 的 Chubby 一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。
ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
另外,ZooKeeper 将数据保存在内存中,性能是非常棒的。在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。
面试题总结
Zookeeper 是什么
ZooKeeper 是一个典型的分布式一致性的解决方案,分布式应用程序可以基于她实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁、分布式队列等功能。它是集群的管理者,监视着集群中各个节点的状态,并根据节点提交的反馈进行下一步合理操作。它为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,并提供一系列简单易用的接口给用户。
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。
客户端的写请求,会同时发给集群中所有zookeeper机器并且达成一致后,请求才会返回成功。
因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。
有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(ZookeeperTransactionId)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。
Zookeeper 的特性有什么
ZooKeeper 可以保证如下分布式一致性特性:
顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到 ZooKeeper 中去。 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,另外一部分没有应用的情况。 单一视图 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 实时性: ZooKeeper 仅仅保证在一定的时间段内,客户端最终一定能够从服务器上读取到最新的数据状态。
参考
An Illustrated Proof of the CAP Theorem JavaGuide 《从 Paxos 到 ZooKeeper》