在分布式系统中,服务(或组件)之间的协调是非常重要的,它构成了分布式系统的基础。
分布式系统中的leader选举、分布式锁、分布式队列等,均需要通过协调服务(Coordination Service)实现。
然而,由于分布式环境的复杂性,尤其是在网络故障、死锁、竞争等已变为常见现象的情况下,实现一个鲁棒(强壮或稳健)的协调服务是极其困难的事情。为了实现一个通用的分布式协调服务,避免每个分布式系统从头实现造成不必要的工作冗余,Hadoop生态系统提供了ZooKeeper。
分布式协调服务ZooKeeper
分布式协调服务是分布式应用中不可缺少的,通常担任协调者的角色,比如leader选举、负载均衡、服务发现、分布式队列和分布式锁等,接下来以leader选举和负载均衡为例,说明分布式协调服务的存在意义及基本职责。
leader选举
在分布式系统中,常见的一种软件设计架构为master/slave,其中master负责集群管理,slave负责执行具体的任务(比如存储数据、处理数据),HDFS、HBase均采用了该架构。
这种架构存在一个明显缺陷:master是单点。
为了避免master出现故障导致整个集群不可用,常见的优化方式是引入多master:
比如双master:active master和standby master,其中active master对外提供服务,而standby master则作为备用master,一直处于“待命”状态,一旦activemaster出现故障,自己则切换为active master。
引入双master需要解决如下两个难题:
- 如何选举出一个master作为active master?
一种常见的解决思路是实现Paxos一致性协议,让多个对等的服务通过某个方式达成一致性,从而选举出一个master。 - 如何发现active master出现故障,如何让standby master安全切换为activemaster?
该问题的难点在于如何避免出现脑裂(split-brain),即集群中同时存在两个active master,造成数据不一致或集群出现混乱的现象。
负载均衡
- 生产者和消费者如何获知最新的消息队列位置?
- 消息队列是分布式的,通常由一组节点构成,这些节点的健康状态是动态变化的,比如某个节点因机器故障变得对外不可用,如何让生产者和消费者动态获知最新的消息队列节点位置是必须要解决的问题。
- 如何让生产者将数据均衡地写入消息队列中各个节点?
- 消息队列提供了一组可存储数据的节点,需让生产者及时了解各个存储节点的负载,以便智能决策将数据均衡地写入这些节点。
- 为了解决以上两个问题,需要引入一个可靠的分布式协调服务,它具备简单的元信息存储和动态获取服务状态等基本功能。
通过leader选举和负载均衡两个常见的分布式问题,我们可以了解到,协调服务对于一个分布式系统而言多重要。为了解决服务协调这一类通用问题,ZooKeeper出现了,它将服务协调的职责从分布式系统中独立出来,以减少系统的耦合性和增强扩充性。
层级命名空间
下图是一个典型的ZooKeeper层级命名空间,整个命名方式类似于文件系统,以多叉树形式组织在一起。
其中,每个节点被称为“znode”,它主要包含以下几个属性:
- data:每个znode拥有一个数据域,记录了用户数据,该域的数据类1型为字节数组。ZooKeeper通过多副本方式保证数据的可靠存储。
- type:znode类型,具体分为persistent, ephemeral, persistent_sequential和ephemeral_sequential四种基本类似,含义如下:
- persistent:持久化节点,能够一直可靠地保存该节点(除非用户显式删除)。
- ephemeral:临时节点,该节点的生命周期与客户端相关,只要客户端保持与ZooKeeper server的session不断开,该节点会一直存在,反之,一旦两者之间连接断开,则该节点也将被自动删除。
- sequential:自动在文件名默认追加一个增量的唯一数字,以记录文件创建顺序,通常与persistent和ephemeral连用,产生- persistent_sequential和ephemeral_sequential两种类型。
version:znode中数据的版本号,每次数据的更新会导致其版本加一。
- children:znode可以包含子节点,但由于ephemeral类型的znode与session的生命周期是绑定的,因此ZooKeeper不允许ephemeral znode有子节点。
- ACL:znode访问控制列表,用户可单独设置每个znode的可访问用户列表,以保证znode被安全访问。ZooKeeper能够保证数据访问的原子性,即一个znode中的数据要么写成功,要么写失败。
Watcher
Watcher是ZooKeeper提供的发布/订阅机制,用户可在某个znode上注册watcher以监听它的变化,一旦对应的znode被删除或者更新(包括删除、数据域被修改、子节点发生变化等), ZooKeeper将以事件的形式将变化内容发送给监听者。需要注意的是,watcher一旦触发后便会被删除,除非用户再次注册该watcher。
Session
Session是Zookeeper中的一个重要概念,它是客户端与ZooKeeper服务端之间的通信通道。同一个session中的消息是有序的。Session具有容错性:如果客户端连接的ZooKeeper服务器宕机,客户端会自动连接到其他活着的服务器上。
leader选举
基于ZooKeeper实现leader选举的基本思想是,让各个参与竞选的实例同时在ZooKeepeer上创建指定的znode,比如/current/leader,谁创建成功则谁竞选成功,并将自己的信息(host、port等)写入该znode数据域,之后其他竞选者向该znode注册watcher,以便当前leader出现故障时,第一时间再次参与竞选。
Zab协议
Zab协议 的全称是 Zookeeper Atomic Broadcast (Zookeeper原子广播)。Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。
Zab协议的核心:定义了事务请求的处理方式
- 所有的事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被叫做 Leader服务器。其他剩余的服务器则是 Follower服务器。
- Leader服务器 负责将一个客户端事务请求,转换成一个 事务Proposal,并将该 Proposal 分发给集群中所有的 Follower 服务器,也就是向所有 Follower 节点发送数据广播请求(或数据复制)
- 分发之后Leader服务器需要等待所有Follower服务器的反馈(Ack请求),在Zab协议中,只要超过半数的Follower服务器进行了正确的反馈后(也就是收到半数以上的Follower的Ack请求),那么 Leader 就会再次向所有的 Follower服务器发送 Commit 消息,要求其将上一个 事务proposal 进行提交。
Zab协议内容
Zab 协议包括两种基本的模式:崩溃恢复 和 消息广播
协议过程
- 当整个集群启动过程中,或者当 Leader 服务器出现网络中弄断、崩溃退出或重启等异常时,Zab协议就会 进入崩溃恢复模式,选举产生新的Leader。
- 当选举产生了新的 Leader,同时集群中有过半的机器与该 Leader 服务器完成了状态同步(即数据同步)之后,Zab协议就会退出崩溃恢复模式,进入消息广播模式。
- 这时,如果有一台遵守Zab协议的服务器加入集群,因为此时集群中已经存在一个Leader服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到Leader服务器,并且完成数据同步。同步完成后,作为新的Follower一起参与到消息广播流程中。
资源管理与调度系统YARN
YARN设计动机
YARN作为一个通用的资源管理系统,其目标是将短作业和长服务混合部署到一个集群中,并为它们提供统一的资源管理和调度功能。YARN是大数据系统发展到一定阶段的必然产物。
1.提高集群资源利用率
在大数据时代,为了存储和处理海量数据,需要规模较大的服务器集群或者数据中心,一般说来,这些集群上运行着数量众多类型纷杂的应用程序和服务,比如离线作业、流式作业、迭代式作业、Crawler Server, Web Server等,传统的做法是,每种类型的作业或者服务对应一个单独的集群,以避免相互干扰。这样,集群被分割成数量众多的小集群:Hadoop集群、HBase集群、Storm集群,Web Server集群等,然而,由于不同类型的作业/服务需要的资源量不同,因此,这些小集群的利用率通常很不均衡,有的集群满负荷、资源紧张,而另外一些则长时间闲置、资源利用率极低,而由于这些集群之间资源无法共享,因此造就了不同时间段不同集群资源利用率不同。为了提高资源整体利用率,一种解决方案是将这些小集群合并成一个大集群,让它们共享这个大集群的资源,并由一个资源统一调度系统进行资源管理和分配,这就诞生了类似于YARN的系统。从集群共享角度看,这类系统实际上将公司的所有硬件资源抽象成一台大型计算机,供所有用户使用。
2.服务自动化部署
一旦将所有计算资源抽象成一个“大型计算机”后,就会产生了一个问题:公司的各种服务如何进行部署?Borg/YARN/Mesos/Torca这类系统需要具备服务自动化部署的功能,因此,从服务部署的角度看,这类系统实际上是服务统一管理系统,这类系统提供服务资源申请、服务自动化部署、服务容错等功能。
YARN基本架构
YARN总体上采用master/slave架构,其中,ResourceManager为master,NodeManager为slave, ResourceManager负责对各个NodeManager上的资源进行统一管理和调度。当用户提交一个应用程序时,需要提供一个用以跟踪和管理这个程序的ApplicationMaster,它负责向ResourceManager申请资源,并要求NodeManager启动可以占用一定资源的任务,由于不同的ApplicationMaster被分布到不同的节点上,因此它们之间不会相互影响。
1. ResourceManager(RM)
RM是一个全局的资源管理器,负责整个系统的资源管理和分配。它主要由两个组件构成:调度器(Scheduler)和应用管理器(Applications Manager, ASM)。
❑ 调度器:调度器主要功能是根据资源容量,队列等方面的限制条件(如每个队列分配一定的资源,最多执行一定数量的作业等),将系统中的资源分配给各个应用程序。YARN中的调度器是一个“纯调度器”,它不再从事任何与具体应用程序相关的工作,比如不负责监控或者跟踪应用的执行状态等,也不负责重新启动因应用执行失败或者硬件故障而产生的失败任务,这些均交由应用程序相关的ApplicationMaster完成。调度器仅根据各个应用程序的资源需求进行资源分配,而资源分配单位用一个抽象概念“资源容器”(Resource Container,简称Container)表示,Container是一个动态资源分配单位,它将内存、CPU、磁盘、网络等资源封装在一起,从而限定每个任务使用的资源量。在YARN中,资源调度器是一个可插拔的组件,用户可根据自己的需要设计新的调度器,YARN提供了多种直接可用的调度器,比如Fair Scheduler和Capacity Scheduler。
❑ 应用程序管理器:应用程序管理器负责管理整个系统中的所有应用程序,包括应用程序提交、与调度器协商资源以启动ApplicationMaster、监控ApplicationMaster运行状态并在失败时重新启动它等。为了避免单个ResourceManager出现单点故障导致整个集群不可用,YARN引入主备ResourceManager实现了HA,当Active ResourceManager出现故障时,StandbyResourceManager会通过ZooKeeper选举,自动提升为Active ResourceManager。
2. ApplicationMaster(AM)
用户提交的每个应用程序均包含一个独立的AM,其主要功能包括:
❑ 与RM调度器协商以获取资源(用Container表示)。
❑ 将得到的资源进一步分配给内部的任务。
❑ 与NM通信以启动/停止任务。
❑ 监控所有任务的运行状态,并在任务运行失败时重新为任务申请资源以重启任务。
3. NodeManager(NM)
NM是每个节点上的资源管理器,一方面,它会定时地向RM汇报本节点上的资源使用情况和各个Container的运行状态,另一方面,它接收并处理来自AM的任务启动/停止等各种请求。在一个集群中,NodeManager通常存在多个,由于YARN内置了容错机制,单个NodeManager的故障不会对集群中的应用程序运行产生严重影响。
4. Container
Container是YARN中的基本资源分配单位,是对应用程序运行环境的抽象,并为应用程序提供资源隔离环境。它封装了多维度的资源,如内存、CPU、磁盘、网络等,当AM向RM申请资源时,RM为AM返回的资源便是用Container表示的。YARN中每个任务均会对应一个Container,且该任务只能使用该Container中描述的资源。需要注意的是,Container不同于MRv1中的slot,它是一个动态资源划分单位,是根据应用程序的需求动态生成的。Container最终是由ContainerExecutor启动和运行的,YARN提供了三种可选的ContainerExecutor:
❑ DefaultContainerExecutor:默认ContainerExecutor实现,直接以进程方式启动Container,不提供任何隔离机制和安全机制,任何应用程序最终均是以YARN服务启动者的身份运行的。
❑ LinuxContainerExecutor:提供了安全和Cgroups隔离的ContainerExecutor,它以应用程序提交者的身份运行Container,且使用Cgroups为Container提供CPU和内存隔离的运行环境。
❑ DockerContainerExecutor:基于Docker实现的ContainerExecutor,可直接在YARN集群中运行Docker Container。Docker是基于Linux Container技术构建的非常轻量级的虚拟机,目前被广泛应用。
YARN高可用
YARN提供了恢复机制,这使得YARN在服务出现故障或人工重启时,不会对正在运行的应用程序产生任何影响。本节将从ResourceManager HA、ResourceManagerRecovery和NodeManager Recovery三个方面讨论YARN高可用方面的设计。
1. ResourceManager HA
ResourceManager负责集群中资源的调度和应用程序的管理,是YARN最核心的组件。由于YARN采用了master/slave架构,这使得ResourceManager成为单点故障。为了避免ResourceManager故障导致整个集群不可用,YARN引入了Active/StandbyResourceManager,通过冗余方式解决ResourceManager单点故障。当Active ResourceManager出现故障时,Standby ResourceManager可通过ZooKeeper选举成为Active ResourceManager,并通过ResourceManager Recovery机制恢复状态。
2. ResourceManager
RecoveryResourceManager内置了重启恢复功能,当ResourceManager就地重启,或发生Active/Standby切换时,不会影响正在运行的应用程序运行。ResourceManagerRecovery主要流程如下:
1)保存元信息:Active ResourceManager运行过程中,会将应用程序的元信息,状态信息以及安全凭证等数据持久化到状态存储系统(state-store)中,YARN支持三种可选的state-store,分别是:
❑ 基于ZooKeeper的state-store:
ZooKeeper是ResourceManager HA必选的state-store,尽管Resource Restart可选择其他state-store,但只有ZooKeeper能防止脑裂(split-brain)现象,即同时存在多个Active ResourceManager状态信息的情况。
❑ 基于FileSystem的state-store:
支持HDFS和本地文件系统两种方式,但不能防止脑裂。
❑ 基于LevelDB的state-store:
基于LevelDB的state-store比前两种方案更加轻量级。LevelDB能更好地支持原子操作,每次更新占用更少的IO资源,生成的文件数目更少。
2)加载元信息:一旦Active ResourceManager重启或出现故障,新启动的Resource-Manager将从存储系统中重新加载应用程序的相关数据,在此过程中,所有运行在各个NodeManager的Container仍正常运行。
3)重构状态信息:新的ResourceManager重启完成后,各个NodeManager会向它重新注册,并将所管理的Container汇报给ResourceManager,这样ResourceManager可动态重构资源分配信息、各个应用程序以及其对应Container等关键数据;同时,ApplicationMaster会向ResourceManager重新发送资源请求,以便ResourceManager重新为其分配资源。
3. NodeManager
RecoveryNodeManager内置了重启恢复功能,当NodeManager就地重启时,之前正在运行的Container不会被杀掉,而是由新的NodeManager接管,并继续正常运行。以上几种高可用机制的实现,使得YARN成为一个通用的资源管理系统,这使得在一个集群中混合部署短作业和长服务变得可能。
YARN工作流程
运行在YARN上的应用程序主要分为两类:短作业和长服务,其中,短作业是指一定时间内(可能是秒级、分钟级或小时级,尽管天级别或者更长时间的也存在,但非常少)可运行完成并退出的应用程序,比如MapReduce作业、Spark作业等;长服务是指不出意外,永不终止运行的应用程序,通常是一些在线服务,比如Storm Servive(主要包括Nimbus和Supervisor两类服务)、HBase Service(包括Hmaster和RegionServer两类服务)[插图]等,而它们本身作为一个框架或服务提供了访问接口供用户使用。
当用户向YARN中提交一个应用程序后,YARN将分两个阶段运行该应用程序:第一个阶段是启动ApplicationMaster;第二个阶段是由ApplicationMaster创建应用程序,为它申请资源,并监控它的整个运行过程,直到运行成功。
1)提交应用程序:用户通过客户端与YARN ResourceManager通信,以提交应用程序,应用程序中需包含ApplicationMaster可执行代码、启动命令和资源需求、应用程序可执行代码和资源需求、优先级、提交到的队列等信息。
2)启动ApplicationMaster:ResourceManager为该应用程序分配第一个Container,并与对应的NodeManager通信,要求它在这个Container中启动应用程序的ApplicationMaster,之后ApplicationMaster的生命周期直接被ResourceManager管理。
3)ApplicationMaster注册:ApplicationMaster启动后,首先,向ResourceManager注册,这样,用户可以直接通过ResourceManage查看应用程序的运行状态,然后,它将初始化应用程序,并按照一定的策略为内部任务申请资源,监控它们的运行状态,直到运行结束,即重复步骤4~7。
4)资源获取:ApplicationMaster采用轮询的方式通过RPC协议向ResourceManager申请和领取资源。
5)请求启动Container:一旦ApplicationMaster申请到资源后,则与对应的NodeManager通信,请求为其启动任务(NodeManager会将任务放到Container中)。
6)启动Container:NodeManager为任务设置好运行环境(包括环境变量、jar包、二进制程序等)后,将任务启动命令写到一个脚本中,并通过ContainerExecutor运行该脚本启动任务。
7)Container监控:ApplicationMaster可通过两种方式获取各个Container的运行状态,以便在任务失败时重新启动任务:
❑ ApplicationMaster与ResourceManager间维护了周期性心跳信息,每次通信可获取自己分管的Container的运行状态。
❑ 各个Container可通过某个RPC协议向ApplicationMaster汇报自己的状态和进度(视具体应用程序而定,比如MapReduce和YARN均实现了该方式)。
8)注销ApplicationMaster:应用程序运行完成后,ApplicationMaster向ResourceManager注销,并退出执行。