随着分布式系统规模的日益扩大,集群中的机器规模也随之变大,因此,如何更好的进行集群管理也显得越来越重要了。

        所谓集群管理,包括集群监控与集群控制两大块,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制。在日常开发和运维过程中,我们经常会有类似于如下的需求。

  • 希望知道当前集群中究竟有多少机器在工作。
  • 对集群中每台机器的运行时状态进行数据收集。
  • 对集群中机器进行上下线操作。

        在传统的基于Agent的分布式集群管理体系中,都是通过在集群中的每台机器上部署一个Agent,由这个Agent负责主动向指定的一个监控中心系统(监控中心系统负责将所有数据进行集中处理,形成一系列报表,并负责实时报警,以下简称“监控中心”)汇报自己所在机器的状态。在集群规模适中的场景下,这确实是一种在生产实践中广泛使用的解决方案,能够快速有效地实现分布式环境集群监控,但是一旦系统的业务场景增多,集群规模变大之后,该解决方案的弊端也就显现出来了。

大规模升级困难

以客户端形式存在的Agent,在大规模使用后,一旦遇上需要大规模升级的情况,就非常麻烦,在升级成本和升级进度的控制上面临巨大的挑战。

统一的Agent无法满足多样的需求

对于机器的CPU使用率、负载(Load)、内存使用率、网络吞吐以及磁盘容量等机器基本的物理状态,使用统一的Agent来进行监控或许都可以满足。但是,如果需要深入应用内部,对一些业务状态进行监控,例如,在一个分布式消息中间件中,希望监控到每个消费者对消息的消费状态;或者在一个分布式任务调度系统汇总,需要对每个机器上任务的执行情况进行监控。很显然,对于这些业务耦合紧密的监控需求,不适合由一个统一的Agent来提供。

编程语言多样性

随着越来越多编程语言的出现,各种异构系统层出不穷。如果使用传统的Agent方式,那么需要提供各种语言的Agent客户端。另一方面,“监控中心”在对异构系统的数据进行整合上面临巨大挑战。

ZooKeeper具有以下两大特性。

  • 客户端如果对ZooKeeper的一个数据节点注册Watcher监听,那么当该数据节点的内容或是其子节点列表发生变更时,ZooKeeper服务器就会向订阅的客户端发送变更通知。
  • 对在ZooKeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么该临时节点也就被自动清除。

        利用ZooKeeper的这两大特性,就可以实现另一种集群机器存活性监控的系统。例如,监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操作,就会在/clusterServers节点下创建一个临时节点:/clusterServer/[Hostname]。这样一来,监控系统就能够实时检测到机器的变动情况,至于后续处理就是监控系统的业务了。下面我们就通过分布式日志收集系统和在线云主机管理这两个典型例子来看看如何使用ZooKeeper实现集群管理。

分布式日志收集系统

        分布式日志收集系统的核心工作就是收集分布在不同机器上的系统日志,在这里我们重点来看分布式日志系统(以下简称“日志系统”)的收集器模块。

        在一个典型的日志系统的架构设计中,整个日志系统会把所有需要收集的日志机器(下文我们以“日志源机器”代表此类机器)分为多个组别,每个组别对应一个收集器,这个收集器其实就是一个后台机器(下文我们以“收集器机器”代表此类机器),用于收集日志。对于大规模分布式日志收集系统场景,通常需要解决如下两个问题。

变化的日志源机器

在生产环境中,伴随着机器的变动,每个应用的机器几乎每天都是在变化的(机器硬件问题、扩容、机房迁移或是网络问题等都会导致一个应用的机器变化),也就是说每个组别中的日志源机器通常是在不断变化的。

变化的收集器机器

日志收集系统自身也会有机器的变更或扩容,于是会出现新的收集器机器加入或是老的收集器机器退出的情况。

        上面两个问题,无论是日志源机器还是收集器机器的变更,最终都归结为一点:如何快速、合理、动态的为每个收集器分配对应的日志源机器,这也成为了整个日志系统正确稳定运转的前提,也是日志收集过程中最大的技术挑战之一。在这种情况下,引入ZooKeeper是个不错的选择,下面我们就来看ZooKeeper在这个场景中的使用。

注册收集器机器

        使用ZooKeeper来进行日志系统收集器的注册,典型做法是在ZooKeeper上创建一个节点作为收集器的根节点,例如/logs/collector(下文我们以“收集器节点”,代表该数据节点),每个收集器机器在启动的时候,都会在收集器节点下创建自己的节点,例如/logs/collector/[Hostname],如下图所示。

zookeeper集群 容器化部署 zookeeper集群管理_zookeeper

任务分发

        待所有收集器机器都创建好自己对应的节点后,系统根据收集器节点下子节点的个数,将所有日志源机器分成对应的若干组,然后将分组后的机器列表分别写到这些收集器机器创建的子节点(例如/logs/collector/host1)上去。这样一来,每个收集器机器都能够从自己对应的收集器节点上获取日志源机器列表,进而开始进行日志收集工作。

状态汇报

        完成收集器机器的注册以及任务分发后,我们还要考虑到这些机器随时都有挂掉的可能。因此,针对这个问题,我们需要有一个收集器的状态汇报机制:每个收集器机器在创建完自己的专属节点后,还需要在对应的子节点上创建一个状态子节点,例如/logs/collector/host1/status,每个收集器机器都需要定期向该节点写入自己的状态信息。我么你可以把这种策略看作是一种心跳检测机制,通常收集器机器都会在这个节点中写入日志收集进度信息。日志系统根据该状态子节点的最后更新时间来判断对应的收集器机器是否存活。

动态分配

        如果收集器机器挂掉或是扩容了,就需要动态的进行收集任务的分配。在运行过程中,日志系统始终关注着/logs/collector这个节点下所有子节点的变更,一旦检测到有收集器机器停止汇报或是有新的收集器机器加入,就要开始任务的重新分配。无论是针对收集器机器停止汇报还是新机器加入的情况,日志系统都需要将之前分配给该收集器的所有任务进行转移。为了解决这个问题,通常有两种做法。

全局动态分配

这是一种简单粗暴的做法,在出现收集器机器挂掉或是新机器加入的时候,日志系统需要根据新的收集器机器列表,立即对所有的日志源机器重新进行一次分组,然后将其分配给剩下的收集器机器。

局部动态分配

全局动态分配方式虽然策略简单,但是存在一个问题:一个或部分收集器机器的变更,就会导致全局动态任务的分配,影响面比较大,因此风险也就比较大。所谓局部动态分配,顾名思义就是在小范围内进行任务的动态分配。在这种策略中,每个收集器机器在汇报自己日志收集状态的同时,也会把自己的负载汇报上去,请注意,这里提到的负载并不仅仅只是简单地指机器CPU负载(Load),而是一个对当前收集器任务执行的综合评估,这个评估算法和ZooKeeper本身并没有太大的关系,这里不再赘述。

在这种策略中,如果一个收集器机器挂了,那么日志系统就会把之前分配给这个机器的任务重新分配到那些负载较低的机器上去。同样,如果有新的收集器机器加入,会从那些负载高的机器上转移部分任务给这个新加入的机器。

注意事项

        在上面的介绍中,我们已经了解了ZooKeeper是如何协调一个分布式日志收集系统工作的,接下来再来看看一些细节问题。

节点类型

我们首先来看/logs/collector这个节点下面子节点的节点类型。在上面已经提到,/logs/collector节点下面的所有子节点都代表了每个收集器机器,那么初步认为这些子节点必须选择临时节点,原因是日志系统可以根据这些临时节点来判断收集器机器的存活性。但是,同时还需要注意的一点是:在分布式日志收集这个场景中,收集器节点上还会存放所有已经分配给该收集器机器的日志源机器列表,如果只是简单地依靠ZooKeeper自身的临时节点机制,那么当一个收集器机器挂掉或是当这个收集器机器中断“心跳汇报”的时候,待该收集器节点的会话失效后,ZooKeeper就会立即删除该节点,又是,记录在该节点上的所有日志源机器列表也就随之被清除掉了。

从上面的描述中可以知道,临时节点显然无法满足这里的业务需求,所以我们选择了使用持久节点来标识每一个收集器机器,同时在这个持久节点下面分别创建/logs/collector/[Hostname]/status节点来表征每一个收集器机器的状态。这样一来,既能实现日志系统对所有收集器的监控,同时在收集器机器挂掉后,依然能够准确的将分配于其中的任务还原。

日志系统节点监听

在实际生产运行过程中,每一个收集器机器更改自己状态节点的频率可能非常高(如每秒1次或更短),而且收集器的数量可能非常大,如果日志系统监听所有这些节点变化,那么通知的消息量可能会非常大。另一方面,在收集器机器正常工作的情况下,日志系统没有必要去实时的接收每次节点状态变更,因此大部分这些状态变更通知都是无用的。因此我们考虑放弃监听设置,而是采用日志系统主动轮询收集器节点的策略,这样就节省了不少网卡流量,唯一的缺陷就是有一定的延时(考虑到分布式日志收集系统的定位,这个延时是可以接受的)。

在线云主机管理

        在线云主机管理通常出现在那些虚拟主机提供商的应用场景中。在这类集群管理中,有很重要的一块就是集群机器的监控。这个场景通常对于集群中的机器状态,尤其是机器在线率的统计有较高的要求,同时需要能够快速的对集群中机器的变更做出响应。

        在传统的实现方案中,监控系统通过某种手段(比如检测主机的指定端口)来对每台机器进行定时检测,或者每台机器自己定时向监控系统汇报“我还活着”。但是这种方式需要每一个业务系统的开发人员自己来处理网络通信、协议设计、调度和容灾等诸多琐碎的问题。下面来看看使用ZooKeeper实现的另一种集群机器存活性监控系统。针对这个系统,我们的需求点通常如下。

  • 如何快速的统计出当前生产环境一共有多少台机器?
  • 如何快速的获取到机器上/下线的情况?
  • 如何实时监控集群中每台主机的运行时状态?

机器上/下线

        为了实现自动化的线上运维,我们必须对机器的上/下线情况有一个全聚德监控。通常在新增机器的时候,需要首先将指定的Agent部署到这些机器上去。Agent部署启动之后,会首先向ZooKeeper的指定节点进行注册,具体的做法就是在机器列表节点下面创建一个临时子节点,例如/XAE/machine/[Hostname](下文我们以“主机节点”代表这个节点),如下图所示。

zookeeper集群 容器化部署 zookeeper集群管理_zookeeper_02

        当Agent在ZooKeeper上创建完这个临时子节点后,对/XAE/machines节点关注的监控中心就会接收到“子节点变更”事件,即上线通知,于是就可以对这个新加入的机器开启相应的后台管理逻辑。另一方面,监控中心同样可以获取到机器下线的通知,这样便实现了对机器上/下线的检测,同时能够很容易的获取到在线的机器列表,对于大规模的扩容和容量评估都有很大的帮助。

机器监控

        对于一个在线云主机系统,不仅要对机器的在线装填进行检测,还需要对机器的运行时状态进行监控。在运行的过程中,Agent会定时将主机的运行状态信息写入ZooKeeper上的主机节点,监控中心通过订阅这些节点的数据变更通知来间接的获取主机的运行时信息。

        随着分布式系统规模变得越来越庞大,对集群机器的监控和管理显得越来越重要。上面提到的这种借助ZooKeeper来实现的方式,不仅能够实时的检测到集群中机器的上/下线情况,而且能够实时的获取到主机的运行时信息,从而能够构建出一个大规模集群的主机图谱。