一、简介
在计算机诞⽣之后很长的⼀段时间⾥,⼀个应⽤服务是在⼀个独⽴的单处理器计算机上运⾏⼀段程序。时⾄今⽇,应⽤服务已经发⽣了很⼤的变化。
在⼤数据和云计算盛⾏的今天,应⽤服务由很多个独⽴的程序组成,这些独⽴的程序则运⾏在形形⾊⾊、千变万化的⼀组计算机上相对于开发在⼀台计算机上运⾏的单个程序,如何让⼀个应⽤中多个独⽴的程序协同⼯作是⼀件⾮常困难的事情。开发这样的应⽤很容易让很多开发⼈员陷⼊如何使多个程序协同⼯作的逻辑中,最后导致没有时间更好地思考和实现他们⾃⼰的应⽤程序逻辑;又或者开发⼈员对协同逻辑关注不够,只是⽤很少的时间开发了⼀个简单脆弱的主协调器,导致不可靠的单⼀失效点ZooKeeper的设计保证了其健壮性,这就使得应⽤开发⼈员可以更多关注应⽤本身的逻辑,⽽不是协同⼯作上。
ZooKeeper从⽂件系统API得到启 发,提供⼀组简单的API,使得开发⼈员可以实现通⽤的协作任务,包括选举主节点、管理组内成员关系、管理元数据等。ZooKeeper包括⼀个应⽤开发库(主要提供Java和C两种语⾔的API)和⼀个⽤Java实现的服务组件。ZooKeeper的服务组件运⾏在⼀组专⽤服务器之上,保证了⾼容错性和 可扩展性。
当你决定使⽤ZooKeeper来设计应⽤时,最好将应⽤数据和协同数据独⽴开。⽐如,⽹络邮箱服务的⽤户对⾃⼰邮箱中的内容感兴趣,但是并不 关⼼由哪台服务器来处理特定邮箱的请求。在这个例⼦中,邮箱内容就是应⽤数据,⽽从邮箱到某⼀台邮箱服务器之间的映射关系就是协同数据 (或称元数据)。整个ZooKeeper服务所管理的就是后者
二、ZooKeeper概述
ZooKeeper官网:https://zookeeper.apache.org/Apache ZooKeeper
致力于开发和运维分布式协调服务的一个开源服务关于ZooKeeper这样的系统功能的讨论都围绕着⼀条主线:它可以在分布式系统中协作多个任务。⼀个协作任务是指⼀个包含多个进程的任务。 这个任务可以是为了协作或者是为了管理竞争。协作意味着多个进程需要 ⼀同处理某些事情,⼀些进程采取某些⾏动使得其他进程可以继续⼯作。 ⽐如,在典型的主-从(master-worker)⼯作模式中,从节点处于空闲状态 时会通知主节点可以接受⼯作,于是主节点就会分配任务给从节点。竞争 则不同。它指的是两个进程不能同时处理⼯作的情况,⼀个进程必须等待 另⼀个进程。同样在主-从⼯作模式的例⼦中,我们想有⼀个主节点,但是 很多进程也许都想成为主节点,因此我们需要实现互斥排他锁(mutual exclusion)。实际上,我们可以认为获取主节点⾝份的过程其实就是获取 锁的过程,获得主节点控制权锁的进程即主节点进程如果你曾经有过多线程程序开发的经验,就会发现很多类似的问题。 实际上,在⼀台计算机上运⾏的多个进程和跨计算机运⾏的多个进程从概念上区别并不⼤。在多线程情况下有⽤的同步原语在分布式系统中也同样 有效。⼀个重要的区别在于,在典型的不共享环境下不同的计算机之间不 共享除了⽹络之外的其他任何信息。虽然许多消息传递算法可以实现同步 原语,但是使⽤⼀个提供某种有序共享存储的组件往往更加简便,这正是ZooKeeper所采⽤的⽅式协同并不总是采取像群⾸选举或者加锁等同步原语的形式。配置元数据也是⼀个进程通知其他进程需要做什么的⼀种常⽤⽅式。⽐如,在⼀个 主-从系统中,从节点需要知道任务已经分配到它们。即使在主节点发⽣崩 溃的情况下,这些信息也需要有效
原语
操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须 是连续的,在执行过程中不允许被中断
ZooKeeper的特点
ZooKeeper的特点:
顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用 了某一个事务,要么都没有应用单一系统映像:无论客户端连到哪一个ZooKeeper服务器上,其看到的服务端数据模型都是一致的可靠性:一旦一次更改请求被应用,其所有的集群服务器上都会被应用利用这些特点,在系统中
数据发布/订阅、负载均衡、服务发现、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能
ZooKeeper改变了什么
使⽤ZooKeeper是否意味着需要以全新的⽅式进⾏应⽤程序开发?事实 并⾮如此,ZooKeeper实际上简化了开发流程,提供了更加敏捷健壮的⽅ 案ZooKeeper之前的其他⼀些系统采⽤分布式锁管理器或者分布式数据库 来实现协作。实际上,ZooKeeper也从这些系统中借鉴了很多概念。但是, ZooKeeper的设计更专注于任务协作,并不提供任何锁的接⼜或通⽤存储数 据接⼜。同时,ZooKeeper没有给开发⼈员强加任何特殊的同步原语,使⽤ 起来⾮常灵活虽然我们也可以不使⽤ZooKeeper来构建分布式系统,但是使⽤ ZooKeeper可以让开发⼈员更专注于其应⽤本⾝的逻辑⽽不是神秘的分布式 系统概念。所以不使⽤ZooKeeper开发分布式系统也并不是不可能,只是难 度会⽐较⼤
关于ZooKeeper名字的来源
ZooKeeper由雅虎研究院开发。小组在进⾏ZooKeeper的开发⼀段 时间之后,开始推荐给其他小组,因此需要为我们的项目起⼀个名字。与此同时,小组也⼀同致⼒于Hadoop项目,参与了很多动物命名的项 目,其中有⼴为⼈知的Apache Pig项目(http://pig.apache.org/)。在讨论各种各样的名字时,⼀位团队成员提到不能再使用动物的名字 了,因为主管觉得这样下去会觉得我们⽣活在动物园中。⼤家对此产⽣了共鸣,分布式系统就像⼀个动物园,混乱且难以管理,而ZooKeeper就是将这⼀切变得可控
ZooKeeper不适⽤的场景
整个ZooKeeper的服务器集群管理着应⽤协作的关键数据。
ZooKeeper不适合⽤作海量数据存储。对于需要存储海量的应⽤数据的情况,有很多备选⽅案,⽐如说数据库和分布式⽂件系统等。因为不同的应⽤有不同的需求,如对⼀致性和持久性的不同需求,所以在设计应⽤时,最佳实践还是应该将应⽤数据和协同数据独⽴开ZooKeeper中实现了⼀组核⼼操作,通过这些可以实现很多常见分布式 应⽤的任务。你知道有多少应⽤服务采⽤主节点⽅式或进程响应跟踪⽅ 式?虽然ZooKeeper并没有为你实现这些任务,也没有为应⽤实现主节点选 举,或者进程存活与否的跟踪的功能,但是,ZooKeeper提供了实现这些任 务的⼯具,对于实现什么样的协同任务,由开发⼈员⾃⼰决定。
关于Apache项⽬
ZooKeeper是⼀个托管到Apache软件基⾦会(Apache Software Foundation)的开源项⽬,Apache的项⽬管理委员会(Project Management Committee,PMC)负责项⽬管理和监督。只有技术专家可以检查补丁 (patch),但是任何开发⼈员都可以贡献补丁。开发⼈员为项⽬做出贡献 后也可以成为技术专家。对项⽬的贡献不仅仅限于贡献补丁,也可以通过 其他形式参与项⽬,与社区成员们互动。我们在邮件列表中进⾏很多讨 论,如新功能特性、⽤户反馈的新问题等。我们强烈建议开发⼈员积极参 与开发社区,订阅邮件列表,并参与讨论。如果你想通过⼀些项⽬与 ZooKeeper社区保持长期关系,你也许会发现成为⼀名技术专家很有价值。
三、ZooKeeper的使用实例
ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以
基于ZooKeeper实现诸如数据发布/订阅、负载均衡、命名服 务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能ZooKeeper一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。服务生产者将自己提供的服务注册到ZooKeeper中心,服务的消费者在进行服务调用的时候先到ZooKeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,使用ZooKeeper担任了注册中心这一角色
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作为 控制器,⽤来实现数据分⽚、故障恢复和服务发现等功能
除了以上介绍的这些应⽤外,还有很多使⽤ZooKeeper的例⼦。根据这些代表应⽤,我们可以从更抽象的层次上讨论ZooKeeper。当开发⼈员使⽤ZooKeeper进⾏开发时,开发⼈员设计的那些应⽤往往可以看成⼀组连接到 ZooKeeper服务器端的客户端,它们通过ZooKeeper的客户端API连接到 ZooKeeper服务器端进⾏相应的操作。
Zookeep的客户端API功能强⼤,其中包括:
保障强⼀致性、有序性和持久性实现通用的同步原语的能⼒在实际分布式系统中,并发往往导致不正确的⾏为。ZooKeeper提供 了⼀种简单的并发处理机制
当然,ZooKeeper也并不是万能的,我们还不能让它解决所有问题。对 我们来说,更重要的是要了解ZooKeeper为我们提供了什么,并知道如何处理其中的⼀些棘⼿问题。这本书的⽬标之⼀就是讨论如何处理这些问题。 我们将介绍ZooKeeper为开发⼈员提供的各种功能,也会讨论我们在开发 ZooKeeper应⽤中遇到的⼀些问题,带你⾛⼊ZooKeeper的世界。
四、通过ZooKeeper构建分布式系统
ZooKeeper可以单点部署,也可以集群部署,集群部署后有多个对外服务提供创建、更新和删除节点的服务
对分布式系统的定义有很多,此处我们对分布式系统的定义为:分布式系统是同时跨越多个物理主机,独⽴运⾏的多个软件组件所组成的系统。我们采⽤分布式去设计系统有很多原因,分布式系统能 够利⽤多处理器的运算能⼒来运⾏组件,⽐如并⾏复制任务。⼀个系统也 许由于战略原因,需要分布在不同地点,⽐如⼀个应⽤由多个不同地点的 服务器提供服务
使⽤⼀个独⽴的协调组件有⼏个重要的好处:⾸先,我们可以独⽴地设计和实现该组件,这样独⽴的组件可以跨多个应⽤共享。其次,系统架 构师可以简化协作⽅⾯的⼯作,这些并不是琐碎的⼩事。最后,系统可以独⽴地运⾏和协作这些组件,独⽴这样的组件,也 简化了⽣产环境中解决实际问题的任务。软件组件以操作系统的进程⽅式运⾏,很多时候还涉及多线程的执⾏。因此ZooKeeper的服务端和客户端也是以进程的⽅式运⾏,⼀个单独的 物理主机(⽆论是⼀个独⽴主机还是⼀个虚拟环境中的操作系统)上运⾏ ⼀个单独的应⽤进程,尽管进程可能采⽤多线程运⾏的⽅式,以便利⽤现 代处理器的多核(multicore)处理能⼒分布式系统中的进程通信有两种选择:直接通过⽹络进⾏信息交换, 或读写某些共享存储。ZooKeeper使⽤共享存储模型来实现应⽤间的协作和 同步原语。对于共享存储本⾝,又需要在进程和存储间进⾏⽹络通信。我 们强调⽹络通信的重要性,因为它是分布式系统中并发设计的基础。
真实系统中要注意的问题
消息延迟:消息传输可能会发⽣任意延迟,⽐如,因为⽹络拥堵。这种任意延迟 可能会导致不可预期的后果。⽐如,根据基准时钟,进程P先发送了⼀个 消息,之后另⼀个进程Q发送了消息,但是进程Q的消息也许会先完成传 送
处理器性能:操作系统的调度和超载也可能导致消息处理的任意延迟。当⼀个进程 向另⼀个进程发送消息时,整个消息的延时时间约等于发送端消耗的时 间、传输时间、接收端的处理时间的总和。如果发送或接收过程需要调度 时间进⾏处理,消息延时会更⾼。
时钟偏移:使⽤时间概念的系统并不少见,⽐如,确定某⼀时间系统中发⽣了哪 些事件。处理器时钟并不可靠,它们之间也会发⽣任意的偏移。因此,依 赖处理器时钟也许会导致错误的决策。
关于这些问题的⼀个重要结果是,在实际情况中,我们很难判断⼀个 进程是崩溃了还是某些因素导致了延时。没有收到⼀个进程发送的消息, 可能是该进程已经崩溃,或是最新消息发⽣了⽹络延迟,或是其他情况导 致进程延迟,或者是进程时钟发⽣了偏移。我们⽆法确定⼀个被称为异步(asynchronous)的系统中的这些区别。数据中⼼通常使⽤⼤量统⼀的硬件。但即使在数据中⼼,我们需要发 现这些问题对应⽤服务带来的影响,因为⼀个应⽤服务使⽤了多代的硬 件,甚⾄对于同⼀批次的硬件也存在微⼩但显著的性能差异。所有这些事 情使分布式系统设计师的⽣活越来越复杂ZooKeeper的精确设计简化了这些问题的处理,ZooKeeper并不是完全 消除这些问题,⽽是将这些问题在应⽤服务层⾯上完全透明化,使得这些 问题更容易处理。ZooKeeper实现了重要的分布式计算问题的解决⽅案,直 观为开发⼈员提供某种程度上实现的封装,⾄少这是我们⼀直希望的
五、演示案例:主-从应用(以Nginx为例)
现在很多服务器需要7*24小时工作,加入一台机器挂了,我们希望能有其他机器顶替他继续工作。此类问题现在多采用master-slave模式,也就是常说的主-从模式,正常情况下主机提供服务器,备机负责监听主机状态,当主机异常时,可以自动切换到备机继续提供服务(有点类似于数据库跟备份,备机正常情况下只监听,不工作),这个切换过程选出下一个主机的过程就是master选举
举个例子:比如我们利用nginx作为代理服务器,把用户的访问代理到Web服务器上,如下图:
Master-Slave机制:
nginx绑定了一个VIP(虚拟IP地址),然后把用户请求代理到Web服务器上,那么如果Nginx出现了故障,不能转发客户请求到Web服务器上,于是整个系统就无法对外提供客户的请求了,系统处于一种不可用的状态,
这就是称之为单点故障面临这种单点故障,我们应该怎么理解呢?也就是我们必须对外提供高可用的服务。一种解决办法是nginx部署多台(例如2台)服务器,当一套故障后另一台对外提供服务,这就是经典的Master-Slave的机制如下图,它们之间使用一个PING/PONG机制,去探测对方是否存在,如果收不到对方的PING或者PONG消息,就认为对端挂掉了
具体的细节看下面的介绍
主节点失效
场景①(状态恢复):主节点失效时,从节点接管主节点的角色,进行故障转移,然而,这并不是简单开始处理进入主节点的请求。从节点需要能够恢复到旧的主节点崩溃时的状态。对于主节点状态的可恢复性,我们不能简单地从已经崩溃的旧主节点来获取这些信息,而需要从其他地方获取,比较好的方法就是从ZooKeeper来获取
场景②(“脑裂”问题):
状态恢复并不是唯一的重要问题。假设主节点有效,从节点却认为主节点已经崩溃。这种错误的假设可能发⽣在以下情况,例如主节点负载很⾼,导致消息任意延迟,导致从节点收不到主节点的PING消息,从节点将认为主节点已经失效,从会接管成为主节点⾓⾊,设置VIP,然后对外提供服务。此时系统中出现了两个主节点针对这个场景中导致的问题,我们⼀般称之为脑裂(splitbrain):系统中两个或者多个部分开始独⽴⼯作,导致整体⾏为不⼀致性我们需要找出⼀种⽅法来处理主节点失效的情况,关键是我们需要避免发⽣脑裂的情况,ZooKeeper就可以来解决这个问题
从节点失效
类似的,在从节点失效时,主节点也要获取从节点失效的信息,这些信息不能简单地通过PING/PONG消息来获取(因为网络可能有延迟),需要从其他地方获取,例如ZooKeeper
六、演示案例:主-从应用(以HBase为例)
我们从理论上介绍了分布式系统,现在,是时候让它更具体⼀点了。 考虑在分布式系统设计中⼀个得到⼴泛应⽤的架构:⼀个主-从(masterworker)架构(如下图)。该系统中遵循这个架构的⼀个重要例⼦是HBase ——⼀个Google的数据存储系统(BigTable)模型的实现,在最⾼层,主 节点服务器(HMaster)负责跟踪区域服务器(HRegionServer)是否可 ⽤,并分派区域到服务器。因本书未涉及这些内容,如欲了解它如何使⽤ ZooKeeper等更多细节,建议查看HBase相关⽂档。我们讨论的焦点是⼀般 的主-从架构
⼀般在这种架构中,主节点进程负责跟踪从节点状态和任务的有效 性,并分配任务到从节点。对ZooKeeper来说,这个架构风格具有代表性, 阐述了⼤多数流⾏的任务,如选举主节点,跟踪有效的从节点,维护应⽤ 元数据
主节点失效
主节点失效时,我们需要有⼀个备份主节点(backup master)。当主 要主节点(primary master)崩溃时,备份主节点接管主要主节点的⾓⾊, 进⾏故障转移,然⽽,这并不是简单开始处理进⼊主节点的请求。新的主 要主节点需要能够恢复到旧的主要主节点崩溃时的状态。对于主节点状态 的可恢复性,我们不能依靠从已经崩溃的主节点来获取这些信息,⽽需要从其他地⽅获取,也就是通过ZooKeeper来获取状态恢复并不是唯⼀的重要问题。假如主节点有效,备份主节点却认 为主节点已经崩溃。这种错误的假设可能发⽣在以下情况,例如主节点负 载很⾼,导致消息任意延迟,备份主节 点将会接管成为主节点的⾓⾊,执⾏所有必需的程序,最终可能以主节点 的⾓⾊开始执⾏,成为第⼆个主要主节点。更糟的是,如果⼀些从节点⽆ 法与主要主节点通信,如由于⽹络分区(network partition)错误导致,这 些从节点可能会停⽌与主要主节点的通信,⽽与第⼆个主要主节点建⽴主从关系。针对这个场景中导致的问题,我们⼀般称之为脑裂(splitbrain):系统中两个或者多个部分开始独⽴⼯作,导致整体⾏为不⼀致 性。我们需要找出⼀种⽅法来处理主节点失效的情况,关键是我们需要避 免发⽣脑裂的情况
从节点失效
客户端向主节点提交任务,之后主节点将任务派发到有效的从节点 中。从节点接收到派发的任务,执⾏完这些任务后会向主节点报告执⾏状 态。主节点下⼀步会将执⾏结果通知给客户端。如果从节点崩溃了,所有已派发给这个从节点且尚未完成的任务需要 重新派发。其中⾸要需求是让主节点具有检测从节点的崩溃的能⼒。主节 点必须能够检测到从节点的崩溃,并确定哪些从节点是否有效以便派发崩溃节点的任务。⼀个从节点崩溃时,从节点也许执⾏了部分任务,也许全 部执⾏完,但没有报告结果。如果整个运算过程产⽣了其他作⽤,我们还 有必要执⾏某些恢复过程来清除之前的状态。
通信故障
如果⼀个从节点与主节点的⽹络连接断开,⽐如⽹络分区(network partition)导致,重新分配⼀个任务可能会导致两个从节点执⾏相同的任 务。如果⼀个任务允许多次执⾏,我们在进⾏任务再分配时可以不⽤验证 第⼀个从节点是否完成了该任务。如果⼀个任务不允许,那么我们的应⽤ 需要适应多个从节点执⾏相同任务的可能性。通信故障导致的另⼀个重要问题是对锁等同步原语的影响。因为节点 可能崩溃,⽽系统也可能⽹络分区(network partition),锁机制也会阻⽌ 任务的继续执⾏。因此ZooKeeper也需要实现处理这些情况的机制。⾸先, 客户端可以告诉ZooKeeper某些数据的状态是临时状态(ephemeral);其 次,同时ZooKeeper需要客户端定时发送是否存活的通知,如果⼀个客户端 未能及时发送通知,那么所有从属于这个客户端的临时状态的数据将全部 被删除。通过这两个机制,在崩溃或通信故障发⽣时,我们就可以预防客 户端独⽴运⾏⽽发⽣的应⽤宕机。回想⼀下之前讨论的内容,如果我们不能控制系统中的消息延迟,就 不能确定⼀个客户端是崩溃还是运⾏缓慢,因此,当我们猜测⼀个客户端 已经崩溃,⽽实际上我们也需要假设客户端仅仅是执⾏缓慢,其在后续还 可能执⾏⼀些其他操作
任务总结,根据之前描述的这些,我们可以得到以下主-从架构的需求:
主节点选举:这是关键的⼀步,使得主节点可以给从节点分配任务。
崩溃检测:主节点必须具有检测从节点崩溃或失去连接的能⼒。
组成员关系管理:主节点必须具有知道哪⼀个从节点可以执⾏任务的能⼒。
元数据管理:主节点和从节点必须具有通过某种可靠的⽅式来保存分配状态和执⾏ 状态的能⼒。
七、演示案例:服务发现上的负载均衡
先来看一个例子,比如我们的一个游戏服务系统是一个分布式的系统,结构如下所示:
里面的GameServer在处理业务时需要调用DataServer的接口update数据,也就是说GameServer必须知道DataServer的IP地址和端口,常用的做法是把IP、Port写入配置文件。例如:
<?xml version="1.0" encoding="UTF-8"?>
<root version="1.0">
<!--GameServer监听端口-->
<HostServer type="1" id="1">
<bind ip="192.168.1.134" port="8500" type="0" />
<register ip="192.168.1.134" />
</HostServer>
<!--连接DataServer的端口-->
<DataServer>
<connect ip="192.168.1.135" port="8600" type ="0" />
</DataServer>
<RecvBuffer>
<buf msgHeadLen="12" clientBuf="40960" serverBuf="1024000" />
</RecvBuffer>
</root>
上面的处理看起来似乎没有什么问题,但是是有很多问题的:
问题①:GameServer启动的时候就去连接DataServer,连接并去发送消息可能没有什么问题,如果DataServer启动时端口是监听起来了,可是启动后,发现DataServer中处理业务消息的某个模块不可用。此时GameServer并不知道这个情况,反正已经开始发送业务消息给DataServer了。像这种情况,我们可以称之为“优雅发布问题”
问题②:游戏服务整体已经可用了,但是研发内部说要整治下服务,需要重新梳理各个系统的IP配置,我们惯用做法还是去更新这个配置文件中的IP值,然后重启GameServer使之生效,在这个过程中势必导致GameServer的短暂不可用。重启之后其他节点都需要停止并更新GameServer的信息,因此将信息写死到配置文件的做法不可取。这样一种情况,我们称之为“优雅更新配置问题”。当然增加DataServer的部署也会导致这个问题
问题③:比如,在游戏过程中,突然DataServer宕机了,或者DataServer应用崩溃了,导致对外不可用,那么GameServer如何感知到,并且不再发送请求到DataServer呢?这个问题其实就是服务健康检查,比如nginx也提供了健康检查
上面三个问题我们可以用服务发现的机制去解决,那么什么叫做服务主动发现以及如何设计实现呢?而且如果DataServer是一种异构的服务配置,也就是说它们的硬件配置不一样,这样GameServer就必须通过权重配比的方式去发送请求给多个DataServer。那利用ZooKeeper是怎么做的呢?
八、分布式协作的难点
当开发分布式应⽤时,其复杂性会⽴即突显出来。例如,当我们的应 ⽤启动后,所有不同的进程通过某种⽅法,需要知道应⽤的配置信息,⼀ 段时间之后,配置信息也许发⽣了变化,我们可以停⽌所有进程,重新分 发配置信息的⽂件,然后重新启动,但是重新配置就会延长应⽤的停机时间与配置信息问题相关的是组成员关系的问题,当负载变化时,我们希 望增加或减少新机器和进程当你⾃⼰实现分布式应⽤时,这个问题仅仅被描述为功能性问题,你 可以设计解决⽅案,部署前你测试了你的解决⽅案,并⾮常确定地认为你 已经正确解决了问题。当你在开发分布式应⽤时,你就会遇到真正困难的 问题,你就不得不⾯对故障,如崩溃、通信故障等各种情况。这些问题会 在任何可能的点突然出现,甚⾄⽆法列举需要处理的所有的情况
注意:拜占庭将军问题
拜占庭将军问题(Byzantine Faults)是指可能导致⼀个组件发⽣任意 ⾏为(常常是意料之外的)的故障。这个故障的组件可能会破坏应用的状 态,甚⾄是恶意⾏为。系统是建立在假设会发⽣这些故障,需要更⾼程度 的复制并使用安全原语的基础上。尽管我们从学术⽂献中知道,针对拜占庭将军问题技术发展已经取得了巨⼤进步,我们还是觉得没有必要在 ZooKeeper中采用这些技术,因此,我们也避免代码库中引⼊额外的复杂 性
在独⽴主机上运⾏的应⽤与分布式应⽤发⽣的故障存在显著的区别: 在分布式应⽤中,可能会发⽣局部故障,当独⽴主机崩溃,这个主机上运 ⾏的所有进程都会失败,如果是独⽴主机上运⾏多个进程,⼀个进程执⾏ 的失败,其他进程可以通过操作系统获得这个故障,操作系统提供了健壮 的多进程消息通信的保障。在分布式环境中这⼀切发⽣了改变:如果⼀个 主机或进程发⽣故障,其他主机继续运⾏,并会接管发⽣故障的进程,为 了能够处理故障进程,这些仍在运⾏的进程必须能够检测到这个故障,⽆ 论是消息丢失或发⽣了时间偏移。理想的情况下,我们基于异步通信的假设来设计系统,即我们使⽤的 主机有可能发⽣时间偏移或通信故障。我们做出这个假设是因为这⼀切的 确会发⽣,时间偏移时常会发⽣,我们偶尔就会遇到⽹络问题,甚⾄更不 幸的,发⽣故障。我们可以做什么样的限制呢?我们来看⼀个最简单的情况。假设我们有⼀个分布式的配置信息发⽣ 了改变,这个配置信息简单到仅仅只有⼀个⽐特位(bit),⼀旦所有运⾏ 中的进程对配置位的值达成⼀致,我们应⽤中的进程就可以启动。这个例⼦原本是⼀个在分布式计算领域⾮常著名的定律,被称为 FLP(由其作者命名:Fischer,Lynch,Patterson),这个结论证明了在异步通信的分布式系统中,进程崩溃,所有进程可能⽆法在这个⽐特位的配 置上达成⼀致。类似的定律称为CAP,表⽰⼀致性(Consistency)、可 ⽤性(Availability)和分区容错性(Partition-tolerance),该定律指出,当 设计⼀个分布式系统时,我们希望这三种属性全部满⾜,但没有系统可以 同时满⾜这三种属性。因此ZooKeeper的设计尽可能满⾜⼀致性和可⽤ 性,当然,在发⽣⽹络分区时ZooKeeper也提供了只读能⼒因此,我们⽆法拥有⼀个理想的故障容错的、分布式的、真实环境存 在的系统来处理可能发⽣的所有问题。但我们还是可以争取⼀个稍微不那 么宏伟的⽬标。⾸先,我们只好对我们的假设或⽬标适当放松,例如,我 们可以假设时钟在某种范围内是同步的,我们也可以牺牲⼀些⽹络分区容 错的能⼒并认为其⼀直是⼀致的,当⼀个进程运⾏中,也许多次因⽆法确 定系统中的状态⽽被认为已经发⽣故障。虽然这些是⼀些折中⽅案,⽽这 些折中⽅案允许我们建⽴⼀些印象⾮常深刻的分布式系统
九、ZooKeeper的成功和注意事项
不得不指出,完美的解决⽅案是不存在的,我们重申ZooKeeper⽆法解决分布式应⽤开发者⾯对的所有问题,⽽是为开发者提供了⼀个优雅的框 架来处理这些问题。多年以来,ZooKeeper在分布式计算领域进⾏了⼤量的⼯作。
Paxos算法和虚拟同步技术(virtual synchrony)给ZooKeeper的设计带来了很⼤影响,通过这些技术可以⽆缝地处理所发⽣的某些变化或情况,并提供给开发者⼀个框架,来应对⽆法⾃动处理的某些情况
ZooKeeper最初由雅虎研究院开发,⽤于处理⼤量的⼤型分布式应⽤。 我们注意到,这些应⽤在分布式协作⽅⾯的处理⽅式并不妥当,这些系统 的部署存在单点故障问题或很脆弱,另⼀⽅⾯,开发者在分布式协作⽅⾯ 花费了⼤量的时间和精⼒,导致开发者没有⾜够的资源来关注应⽤本⾝的 功能逻辑。我们还注意到,这些应⽤都在基本协作⽅⾯有相同的需求。因 此,我们开始着⼿设计⼀套通⽤的解决⽅案,通过某些关键点让我们可以 ⼀次实现就能应⽤于⼤多数不同的应⽤中。ZooKeeper已经被证实更加通 ⽤,其受欢迎程度超越了我们的想象多年来,我们发现⼈们可以很容易地部署ZooKeeper集群,轻松通过这 个集群开发应⽤,但实际上,在使⽤ZooKeeper时,有些情况ZooKeeper⾃⾝⽆法进⾏决策⽽是需要开发者⾃⼰做出决策,有些开发者并不完全了解这些