Apache Ambari是一种基于Web的工具,支持Apache Hadoop集群的供应、管理和监控。Ambari已支持大多数Hadoop组件,包括HDFS、MapReduce、Hive、Pig、 Hbase、Zookeeper、Sqoop和Hcatalog等。
1. Resource:Ambari把可以被管理的资源的抽象为一个Resource实例,资源可以包括服务、组件、主机节点等,一个resource实例中包含了一系列该资源的属性;
2. Property:服务组件的指标名称;
**3. ResourceProvider和PropertyProvider:**分别对应Resource和Property的提供方,获取指标需要先获取Resource,然后获取Property对应的metric;
**4. Query:**Query是Resource的内部对象,代表了对该资源的操作;
**5. Request:**一个Request代表了对Resource的操作请求,包含http信息及要操作的Resource的实例,Request按照http的请求方式分为四种:GET、PUT、DELETE、POST;
**6. Predicate:**一个Predicate代表了一系列表达式,如and、or等;
Ambari 可以分为 5个大的组件,分别是是 Ambari-server 、 Ambari-web 、 Ambari-agent 、 Ambari-metrics-collector 和 Ambari-metrics-monitor 。
在集群的每一台机器上都会部署 Ambari-agent 程序。 Agent 主要负责接收来着 Server 端的命令,这些命令可以是安装、启动、停止 Hadoop 集群上的某一服务。同时, agent 端需要向 Ambari-server 端上 报命令执行的结果,是执行成功还是失败。
Ambari-Server 提供 REST 接口给Agent 和 Web 访问,用户甚至可以不用界面,而是通过 curl 命令来操控集群。
Ambari-metric-collector和 Ambari-metrics-monitor 是收集群中组件 metrics 的模块。
Ambari充分利用了一些已有的优秀开源软件,巧妙地把它们结合起来,使其在分布式环境中做到了集群式服务管理能力、监控能力、展示能力,这些优秀的开源软件有:
(1)agent端,采用了puppet管理节点。
(2)在web端,采用ember.js作为前端MVC框架和NodeJS相关工具,用handlebars.js作为页面渲染引擎,在CSS/HTML方面还用了Bootstrap框架。
(3)在Server端,采用了Jetty、Spring、JAX-RS等。
(4)同时利用了Ganglia、Nagios的分布式监控能力。
Ambari架构采用的是Server/Client的模式,主要由两部分组成:ambari-agent和ambari-server。ambari依赖其它已经成熟的工具,例如其ambari-server 就依赖python,而ambari-agent还同时依赖ruby, puppet,facter等工具,还有它也依赖一些监控工具nagios和ganglia用于监控集群状况。
其中:
ambari-server | Ambari的Server程序,主要管理部署在每个节点上的管理监控程序 |
---|---|
ambari-agent | 部署在监控节点上运行的管理监控程序 |
ambari-web | Ambari页面UI的代码,作为用户与Ambari server交互的。 |
ambari-views | 用于扩展Ambari Web UI中的框架 |
ambari-common | Ambari-server 和Ambari-agent 共用的代码 |
ambari-metrics | 在Ambari所管理的集群中用来收集、聚合和服务Hadoop和系统计量 |
contrib | 自定义第三方库 |
docs | 文档 |
目录 | 描述 |
---|---|
org.apache.ambari.server.api.services | 对web接口的入口方法,处理/api/v1/* 的请求 |
org.apache.ambari.server.controller | 对Ambari中cluster的管理处理,如新增host,更service、删除component等 |
org.apache.ambari.server.controller.internal | 主要存放ResourceProvider和PropertyProvider; |
org.apache.ambari.service.orm.* | 对数据库的操作 |
org.apache.ambari.server.agent.rest | 处理与Agent的接口的入口方法 |
org.apache.ambari.security | 使用Spring Security来做权限管理 |
每一种Resource都对应一个ResourceProvider,如下表所示:
Resource.Type | ResourceProvider |
---|---|
Workflow | WorkflowResourceProvider |
Job | JobResourceProvider |
TaskAttempt | TaskAttemptResourceProvider |
View | ViewResourceProvider |
ViewInstance | ViewInstanceResourceProvider |
Blueprint | BlueprintResourceProvider |
Cluster | ClusterResourceProvider |
Service | ServiceResourceProvider |
Component | ComponentResourceProvider |
Host | HostResourceProvider |
HostComponent | HostComponentResourceProvider |
Configuration | ConfigurationResourceProvider |
Action | ActionResourceProvider |
Request | RequestResourceProvider |
Task | TaskResourceProvider |
User | UserResourceProvider |
Stack | StackResourceProvider |
StackVersion | StackVersionResourceProvider |
StackService | StackServiceResourceProvider |
StackServiceComponent | StackServiceComponentResourceProvider |
StackConfiguration | StackConfigurationResourceProvider |
OperatingSystem | OperatingSystemResourceProvider |
Repository | RepositoryResourceProvider |
RootService | RootServiceResourceProvider |
RootServiceComponent | RootServiceComponentResourceProvider |
RootServiceHostComponent | RootServiceHostComponentResourceProvider |
ConfigGroup | ConfigGroupResourceProvider |
RequestSchedule | RequestScheduleResourceProvider |
ambari-server是一个有状态的,它维护着自己的一个有限状态机FSM,同时这些状态机存储在数据库中,默认数据库为postgressql数据库。
Ambari-Server是一个WEB Server,提供统一的REST API接口,同时向web和agent开放了两个不同的端口(默认前者是8080, 后者是8440或者8441)。它是由Jetty Server容器构建起来的,通过Spring Framework构建出来的WEB服务器,其中大量采用了google提供的Guice注解完成spring框架所需要的注入功能,REST服务由JAX-RS标准来实现。
如下图所示,server端主要维护三类状态:
Ambari-server的Heartbeat Handler模块用于接收各个agent的心跳请求(心跳请求里面主要包含两类信息:节点状态信息和返回的操作结果),把节点状态信息传递给FSM状态机去维护着该节点的状态,并且把返回的操作结果信息返回给Action Manager去做进一步的处理。
Coordinator模块又可以称为API handler,主要在接收WEB端操作请求后,会检查它是否符合要求,stage planner分解成一组操作,最后提供给Action Manager去完成执行操作。
因此,从上图就可以看出,Ambari-Server的所有状态信息的维护和变更都会记录在数据库中,用户做一些更改服务的操作都会在数据库上做一些相应的记录,同时,agent通过心跳来获得数据库的变更历史。
Ambari Server 会读取 Stack 和 Service 的配置文件。当用 Ambari 创建集群的时候,Ambari Server 传送 Stack 和 Service 的配置文件以及 Service 生命周期的控制脚本到 Ambari Agent。Agent 拿到配置文件后,会下载安装公共源里软件包(Redhat,就是使用 yum 服务)。安装完成后,Ambari Server 会通知 Agent 去启动 Service。之后 Ambari Server 会定期发送命令到 Agent 检查 Service 的状态,Agent 上报给 Server,并呈现在 Ambari 的 GUI 上。
Ambari Server 支持 Rest API,这样可以很容易的扩展和定制化 Ambari。甚至于不用登陆 Ambari 的 GUI,只需要在命令行通过 curl 就可以控制 Ambari,以及控制 Hadoop 的 cluster。具体的 API 可以参见 Apache Ambari 的官方网页 API reference。
ambari-agent是无状态的,其功能主要分两部分:
因此它有两种队列:
分别是用SSH和人工手动的非SSH
步骤:
人工手动引导
具体步骤内容基本同上
步骤
Ambari-web使用了一个流行的前端Embar.js MVC框架实现,Embar.js是一个TodoMVC框架,它涵盖了现今典型的单页面应用(single page application)几乎所有的行为。
使用了nodejs
使用brunch 作为项目的构建管理工具
Brunch ,是一个超快的HTML5构建工具。它有如下功能:
(1)编译你的脚本、模板、样式、链接它们。
(2)将脚本和模板封装进common.js/AMD模块里,链接脚本和样式。
(3)为链接文件生成源地图,复制资源和静态文件。
(4)通过缩减代码和优化图片来收缩输出,看管你的文件更改。
(5)并通过控制台和系统提示通知你错误。
Nodejs 是一个基于Chrome JavaScript运行时建立的一个平台,用来方便的搭建快速的易于扩展的网络应用,NodeJS借助事件驱动,非阻塞I/O模型变得轻量和高效,非常适合运行在分布式设备的数据密集型的实时应用。
Agent发送过来的心跳请求由org.apache.ambari.server.agent.HeartBeatHandler.handleHeartBeat(HeartBeat)来处理,执行完后,同时会返回org.apache.ambari.server.agent.HeartBeatResponse给agent。 org.apache.ambari.server.agent.HeartBeat里面主要含了两类信息:节点的状态信息nodeStatus和服务状态信息componentStatus。
public class HeartBeatHandler {
...
public HeartBeatResponse handleHeartBeat(HeartBeat heartbeat)
throws AmbariException {
long now = System.currentTimeMillis();
if (heartbeat.getAgentEnv() != null && heartbeat.getAgentEnv().getHostHealth() != null) {
heartbeat.getAgentEnv().getHostHealth().setServerTimeStampAtReporting(now);
}
String hostname = heartbeat.getHostname();
Long currentResponseId = hostResponseIds.get(hostname);
HeartBeatResponse response;
if (currentResponseId == null) {
//Server restarted, or unknown host.
LOG.error("CurrentResponseId unknown for " + hostname + " - send register command");
// 无responseId, 新请求,就进行注册, responseId =0
return createRegisterCommand();
}
LOG.debug("Received heartbeat from host"
+ ", hostname=" + hostname
+ ", currentResponseId=" + currentResponseId
+ ", receivedResponseId=" + heartbeat.getResponseId());
if (heartbeat.getResponseId() == currentResponseId - 1) {
LOG.warn("Old responseId received - response was lost - returning cached response");
return hostResponses.get(hostname);
} else if (heartbeat.getResponseId() != currentResponseId) {
LOG.error("Error in responseId sequence - sending agent restart command");
// 心跳是历史记录,那么就要求其重启,重新注册,responseId 不变
return createRestartCommand(currentResponseId);
}
response = new HeartBeatResponse();
//responseId 加 1 , 返回一个新的responseId,下次心跳又要把这个responseId带回来。
response.setResponseId(++currentResponseId);
Host hostObject;
try {
hostObject = clusterFsm.getHost(hostname);
} catch (HostNotFoundException e) {
LOG.error("Host: {} not found. Agent is still heartbeating.", hostname);
if (LOG.isDebugEnabled()) {
LOG.debug("Host associated with the agent heratbeat might have been " +
"deleted", e);
}
// For now return empty response with only response id.
return response;
}
//失去心跳,要求重新注册, responseId=0
if (hostObject.getState().equals(HostState.HEARTBEAT_LOST)) {
// After loosing heartbeat agent should reregister
LOG.warn("Host is in HEARTBEAT_LOST state - sending register command");
return createRegisterCommand();
}
hostResponseIds.put(hostname, currentResponseId);
hostResponses.put(hostname, response);
// If the host is waiting for component status updates, notify it
//如果主机正在等待组件状态更新,请通知它
//节点已经进行了注册,但是该节点还没有汇报相关状态信息,等待服务状态更新
if (heartbeat.componentStatus.size() > 0
&& hostObject.getState().equals(HostState.WAITING_FOR_HOST_STATUS_UPDATES)) {
try {
LOG.debug("Got component status updates");
//更新服务状态机
hostObject.handleEvent(new HostStatusUpdatesReceivedEvent(hostname, now));
} catch (InvalidStateTransitionException e) {
LOG.warn("Failed to notify the host about component status updates", e);
}
}
if (heartbeat.getRecoveryReport() != null) {
RecoveryReport rr = heartbeat.getRecoveryReport();
processRecoveryReport(rr, hostname);
}
try {
if (heartbeat.getNodeStatus().getStatus().equals(HostStatus.Status.HEALTHY)) {
//向状态机发送更新事件,更新节点至正常状态
hostObject.handleEvent(new HostHealthyHeartbeatEvent(hostname, now,
heartbeat.getAgentEnv(), heartbeat.getMounts()));
} else { // 把节点列入不健康
hostObject.handleEvent(new HostUnhealthyHeartbeatEvent(hostname, now, null));
}
} catch (InvalidStateTransitionException ex) {
LOG.warn("Asking agent to re-register due to " + ex.getMessage(), ex);
hostObject.setState(HostState.INIT);
return createRegisterCommand();
}
/**
* A host can belong to only one cluster. Though getClustersForHost(hostname)
* returns a set of clusters, it will have only one entry.
*主机只能属于一个集群。 通过getClustersForHost(hostname)返回一组集群,它只有一个条目。
*
* TODO: Handle the case when a host is a part of multiple clusters.
* 处理 主机是多个集群的一部分时的 情况。
*/
Set clusters = clusterFsm.getClustersForHost(hostname);
if (clusters.size() > 0) {
String clusterName = clusters.iterator().next().getClusterName();
if (recoveryConfigHelper.isConfigStale(clusterName, hostname, heartbeat.getRecoveryTimestamp())) {
RecoveryConfig rc = recoveryConfigHelper.getRecoveryConfig(clusterName, hostname);
response.setRecoveryConfig(rc);
if (response.getRecoveryConfig() != null) {
LOG.info("Recovery configuration set to {}", response.getRecoveryConfig().toString());
}
}
}
heartbeatProcessor.addHeartbeat(heartbeat);
// Send commands if node is active
if (hostObject.getState().equals(HostState.HEALTHY)) {
sendCommands(hostname, response);
annotateResponse(hostname, response);
}
return response;
}
...
}
安装ambari-agent 服务时会把相应在的python代码置于python执行的环境上下文中,例如其入口代码可能是/usr/lib/python2.6/site-packages/ambari_agent/main.py,并且进行相关初始化工作(例如验证参数,与server建立连接,初始化安全验证证书),最后会产生一个新的控制器Controller子线程来统一管理节点的状态。Controller线程里面有一个动作队列ActionQueue线程,并且开启向Server注册和发心跳服务。可以看出来,ambari-agent主要由两个线程组成,Controller线程向Server发送注册或心跳请求,请求到的Action数据放到ActionQueue线程里面,ActionQueue线程维护着两个队列:CommandQueue和ResultQueue。ActionQueue线程会监听CommandQueue的状况。
class Controller(threading.Thread):
def __init__(self, config, range=30):
// 在初始化Controller之前,ambari-agent就会在main.py里面进行判断:ambari-server是否正常,正常才会初始化Controller
// 省略初始化代码
def run(self):
try:
// 初始化队列线程
self.actionQueue = ActionQueue(self.config, controller=self)
self.actionQueue.start()
// 初始化注册类
self.register = Register(self.config)
// 初始化心跳类
self.heartbeat = Heartbeat(self.actionQueue, self.config, self.alert_scheduler_handler.collector())
opener = urllib2.build_opener()
urllib2.install_opener(opener)
while True:
self.repeatRegistration = False
//开始注册 并且 定时发心跳
self.registerAndHeartbeat()
if not self.repeatRegistration:
logger.info("Finished heartbeating and registering cycle")
break
except:
logger.exception("Controller thread failed with exception:")
raise
logger.info("Controller thread has successfully finished")
CommandQueue队列主要有3类command:
Ambari-Server接受来自两处的REST请求,Agent过来的请求处理逻辑由包org.apache.ambari.server.agent处理, 而API所的处理逻辑来自org.apache.ambari.server.api。详见如下代码:
“`
“`
Ambari-Server有一个状态机管理模块,所有节点的状态信息更改都最终提供给状态机进行更改操作,因此状态机是一个很忙的组件。在Ambari-Server里面,把每一次更改操作都把它当作是一类事件,采用事件驱动机制完成对应的任务。这种思想有点借鉴已经运用在hadoop 2.x YARN里面的事件驱动机制。事件驱动机制能够一种高效的异步RPC请求方式,直接调用需要执行相应的代码逻辑,而事件驱动只需要产生事件统一提交给事件处理器,因此事件驱动需要一个更复杂的有限状态机结合起来一同使用。