在第一节中,我们简单讲了一下Ambari的系统架构。我们这一节主要分析Ambari的源代码,总览Ambari的具体实现方式及其工作细节。

一、Ambari-Server启动

Ambari-Server是一个WEB Server,提供统一的REST API接口,同时向web和agent开放了两个不同的端口(默认前者是8080, 后者是8440或者8441)。它是由Jetty Server容器构建起来的,通过Spring Framework构建出来的WEB服务器,其中大量采用了google提供的Guice注解完成spring框架所需要的注入功能(想一想,之前spring框架需要加载一个applicationcontext.xml文件来把bean注入进来,现在可以用Guice注解的方式就可以轻松完成)。 REST框架由JAX-RS标准来构建。
 
 Ambari-Server接受来自两处的REST请求,Agent过来的请求处理逻辑由包org.apache.ambari.server.agent处理, 而API所的处理逻辑来自org.apache.ambari.server.api。详见如下代码:
  1. // API handler加载的REST处理包
  2. ServletHolder sh = new ServletHolder(ServletContainer.class);
  3. //采用包结构的形式进行加载
  4. sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
  5. "com.sun.jersey.api.core.PackagesResourceConfig");
  6. // 下面是/api/v1/接受的请求由哪些包来处理
  7. sh.setInitParameter("com.sun.jersey.config.property.packages",
  8. "org.apache.ambari.server.api.rest;" +
  9. "org.apache.ambari.server.api.services;" +
  10. "org.apache.ambari.eventdb.webservice;" +
  11. "org.apache.ambari.server.api");
  12. sh.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature",
  13. "true");
  14. root.addServlet(sh, "/api/v1/*");
  15. sh.setInitOrder(2);
  16. // Agent Handler加载的REST包,主要是org.apache.ambari.server.agent.rest.HeartBeatHandler来接受心跳请求
  17. ServletHolder agent = new ServletHolder(ServletContainer.class);
  18. agent.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
  19. "com.sun.jersey.api.core.PackagesResourceConfig");
  20. agent.setInitParameter("com.sun.jersey.config.property.packages",
  21. "org.apache.ambari.server.agent.rest;" + "org.apache.ambari.server.api");
  22. agent.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature",
  23. "true");
  24. agentroot.addServlet(agent, "/agent/v1/*");
  25. agent.setInitOrder(3);
  26. // 对agent发过来的数据包进行证书签名所需要加载的包
  27. ServletHolder cert = new ServletHolder(ServletContainer.class);
  28. cert.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
  29. "com.sun.jersey.api.core.PackagesResourceConfig");
  30. cert.setInitParameter("com.sun.jersey.config.property.packages",
  31. "org.apache.ambari.server.security.unsecured.rest;" + "org.apache.ambari.server.api");
  32. cert.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature",
  33. "true");
  34. agentroot.addServlet(cert, "/*");
  35. cert.setInitOrder(4);
  36. // WEB客户端提供的数据包
  37. ServletHolder resources = new ServletHolder(ServletContainer.class);
  38. resources.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
  39. "com.sun.jersey.api.core.PackagesResourceConfig");
  40. resources.setInitParameter("com.sun.jersey.config.property.packages",
  41. "org.apache.ambari.server.resources.api.rest;" + "org.apache.ambari.server.api");
  42. resources.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature",
  43. "true");
  44. root.addServlet(resources, "/resources/*");
  45. resources.setInitOrder(6)
 正如上一节所述,Ambari-Server有一个状态机管理模块,所有节点的状态信息更改都最终提供给状态机进行更改操作,因此状态机是一个很忙的组件。在Ambari-Server里面,把每一次更改操作都把它当作是一类事件,采用事件驱动机制完成对应的任务。这种思想有点借鉴已经运用在hadoop 2.x YARN里面的事件驱动机制。事件驱动机制能够一种高效的异步RPC请求方式,直接调用需要执行相应的代码逻辑,而事件驱动只需要产生事件统一提交给事件处理器,因此事件驱动需要一个更复杂的有限状态机结合起来一同使用。

二、Ambari-Server处理Ambari-Agent请求

Agent发送过来的心跳请求由org.apache.ambari.server.agent.HeartBeatHandler.handleHeartBeat(HeartBeat)来处理,执行完后,同时会返回org.apache.ambari.server.agent.HeartBeatResponse给agent。 org.apache.ambari.server.agent.HeartBeat里面主要含了两类信息:节点的状态信息nodeStatus和服务状态信息componentStatus。
  1. public HeartBeatResponse handleHeartBeat(HeartBeat heartbeat)
  2. throws AmbariException {
  3. String hostname = heartbeat.getHostname();
  4. Long currentResponseId = hostResponseIds.get(hostname);
  5. HeartBeatResponse response;
  6. if (currentResponseId == null) {
  7. //Server restarted, or unknown host.
  8. LOG.error("CurrentResponseId unknown - send register command");
  9. return createRegisterCommand();  // 无responseId, 新请求,就进行注册, responseId =0
  10. }
  11. LOG.info("Received heartbeat from host"
  12. + ", hostname=" + hostname
  13. + ", currentResponseId=" + currentResponseId
  14. + ", receivedResponseId=" + heartbeat.getResponseId());
  15. if (heartbeat.getResponseId() == currentResponseId - 1) {
  16. LOG.warn("Old responseId received - response was lost - returning cached response");
  17. return hostResponses.get(hostname);
  18. } else if (heartbeat.getResponseId() != currentResponseId) {
  19. LOG.error("Error in responseId sequence - sending agent restart command");
  20. return createRestartCommand(currentResponseId);   //  心跳是历史记录,那么就要求其重启,重新注册,
  21. //  responseId 不变
  22. }
  23. response = new HeartBeatResponse();
  24. response.setResponseId(++currentResponseId);  // responseId 加 1 , 返回一个新的responseId,下次心跳又要把这个responseId带回来。
  25. Host hostObject = clusterFsm.getHost(hostname);
  26. if (hostObject.getState().equals(HostState.HEARTBEAT_LOST)) {     // 失去心跳
  27. // After loosing heartbeat agent should reregister
  28. LOG.warn("Host is in HEARTBEAT_LOST state - sending register command");
  29. return createRegisterCommand();   //失去连接,要求重新注册, responseId=0
  30. }
  31. hostResponseIds.put(hostname, currentResponseId);
  32. hostResponses.put(hostname, response);
  33. long now = System.currentTimeMillis();
  34. HostState hostState = hostObject.getState();
  35. // If the host is waiting for component status updates, notify it
  36. if (heartbeat.componentStatus.size() > 0
  37. && hostObject.getState().equals(HostState.WAITING_FOR_HOST_STATUS_UPDATES)) {  // 节点已经进行了注册,但是该节点还没有汇报相关状态信息,等待服务状态更新
  38. try {
  39. LOG.debug("Got component status updates");
  40. hostObject.handleEvent(new HostStatusUpdatesReceivedEvent(hostname, now));  // 更新服务状态机
  41. } catch (InvalidStateTransitionException e) {
  42. LOG.warn("Failed to notify the host about component status updates", e);
  43. }
  44. }
  45. try {
  46. if (heartbeat.getNodeStatus().getStatus().equals(HostStatus.Status.HEALTHY)) {
  47. hostObject.handleEvent(new HostHealthyHeartbeatEvent(hostname, now,
  48. heartbeat.getAgentEnv()));    // 向状态机发送更新事件,更新节点至正常状态
  49. } else {
  50. hostObject.handleEvent(new HostUnhealthyHeartbeatEvent(hostname, now,
  51. null));   // 把节点列入不健康
  52. }
  53. if (hostState != hostObject.getState()) scanner.updateHBaseMaster(hostObject);  // 更新 hbase master状态,如果该节点上在master节点的话,
  54. } catch (InvalidStateTransitionException ex) {
  55. LOG.warn("Asking agent to reregister due to " + ex.getMessage(), ex);
  56. hostObject.setState(HostState.INIT);   // 出错,重新注册
  57. return createRegisterCommand();
  58. }
  59. //Examine heartbeat for command reports
  60. processCommandReports(heartbeat, hostname, clusterFsm, now);  // 处理状态更改的汇报信息,看进度到哪一点了。
  61. // Examine heartbeart for component live status reports
  62. processStatusReports(heartbeat, hostname, clusterFsm);  // 处理该节点的服务状态信息(也称作为组件)
  63. // Send commands if node is active
  64. if (hostObject.getState().equals(HostState.HEALTHY)) {
  65. sendCommands(hostname, response);  //把该节点的命令AgentCommand组装起来,统一返回给agent
  66. }
  67. return response;
  68. }
下面我们学习一下Ambari-Agent是如何处理heartbeat请求的。agent是由Python代码所写,每个节点上都会有一个python的daemon进程与server进行交互。

三、Ambari-Agent执行流程

安装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的状况。
  1. class Controller(threading.Thread):
  2. def __init__(self, config, range=30):  // 在初始化Controller之前,ambari-agent就会在main.py里面进行判断:ambari-server是否正常,正常才会初始化Controller
  3. // 省略初始化代码
  4. def run(self):
  5. self.actionQueue = ActionQueue(self.config)  // 初始化队列线程
  6. self.actionQueue.start()
  7. self.register = Register(self.config)  // 初始化注册类
  8. self.heartbeat = Heartbeat(self.actionQueue)  // 初始化心跳类
  9. opener = urllib2.build_opener()
  10. urllib2.install_opener(opener)
  11. while True:
  12. self.repeatRegistration = False
  13. self.registerAndHeartbeat()    //开始注册 并且 定时发心跳
  14. if not self.repeatRegistration:
  15. break
  16. pass

CommandQueue队列主要有3类command:

  1. REGISTER_COMMAND:该类命令主要通知agent重新向server发送注册请求。
  2. STATUS_COMMAND:该类命令主要告诉agent需要向server发送某组件的状态信息。
  3. EXECUTION_COMMAND:要求agent执行puppet或者软件集升级任务

ActionQueue线程在执行STATUS_COMMAND时,会通过LiveStatus类构建一个StatusCheck检测器,并且通过ps命令来检测该组件是否是活着。

  1. def getIsLive(self, pidPath):
  2. // ....
  3. //检测该组件pid文件是否存在...
  4. res = self.sh.run(['ps -p', str(pid), '-f'])   //运行shell命令,检测该进程是否存在
  5. lines = res['output'].strip().split(os.linesep)
  6. try:
  7. procInfo = lines[1]
  8. isLive = not procInfo == None
  9. except IndexError:
  10. logger.info('Process is dead')
  11. return isLive

ActionQueue线程在执行EXECUTION_COMMAND任务时,通常是用于执行相关Puppet任务,它会在[agent].prefix目录下产生一个puppet文件,然后执行puppet apply命令执行一批puppet module文件完成配置更改和节点管理任务。

  1. def runCommand(self, command, tmpoutfile, tmperrfile):
  2. taskId = 0
  3. if command.has_key("taskId"):
  4. taskId = command['taskId']
  5. siteppFileName = os.path.join(self.tmpDir, "site-" + str(taskId) + ".pp") // self.tmpdir是ambari-agent.ini里面配置的agent.prefix参数, site-{taskId:int}.pp文件里面主要是一组服务的配置参数
  6. generateManifest(command, siteppFileName, self.modulesdir, self.config)  //生成一个puppet配置文件
  7. result = self.run_manifest(command, siteppFileName, tmpoutfile, tmperrfile) // 会根据command命令里repo_info参数值,执行相应的puppet命令
  8. return result

Ambari深入学习(II)-实现细节的更多相关文章

  1. Mybatis学习的一些细节

    一.mybatis 基本配置 最近几天一直在学习mybatis,看了一些源码,本文讲述mybatis的一些基本配置和基本的用法和注意到一些细节.个人时间和精力有限,本文属于流水账类型,不成体系,算是自 ...

  2. Ambari深入学习(I)-系统架构

    Ambari是hadoop分布式集群配置管理工具,是由hortonworks主导的开源项目.它已经成为apache基金会的孵化器项目,已经成为hadoop运维系统中的得力助手,引起了业界和学术界的关注 ...

  3. 03-spring学习-属性配置细节

    配置bean的一些细节 字面值 如果包含特殊符号,直接写会报错.可以用这个<![CDATA[]]>包裹起来. 比如这里的配置属性里面的value值包含<>等特殊符号,直接写会报 ...

  4. Ambari深入学习(III)-开源使用及其改进思考

    Ambari采用的不是一个新的思想和架构,也不是完成了软件的新的革命,而是充分利用了一些已有的优秀开源软件,巧妙地把它们结合起来,使其在分布式环境中做到了集群式服务管理能力.监控能力.展示能力.这些优 ...

  5. 近期Android学习II

    一晃眼又过了5天,这几天的学习有些杂乱,半年在家没运动,返校了准备慢慢恢复运动,身体才是革命的本钱~ 四天跑了三回步,每次都死亡喘息= = 这几天的学习重点总归还是放在Android上了,前面31天连 ...

  6. jQuery学习易忘细节

    1.类似于alert(""),但不会中断页面操作:console.log("last"); 2.javascript是HTML5以及所有现代浏览器中的默认脚本语 ...

  7. 【SQLSERVER学习笔记】细节记录

    SQLSERVER 查询时,WHERE中使用<>时,不会把NULL值查出来. SQLSERVER子查询中不能使用 ORDER BY. SQLSERVER 使用DISTINCT时,必须把OR ...

  8. LWIP学习之一些细节

    一 绑定端口后,开启监听,为何监听还要返回一个新的连接?:监听状态的连接只需要很小的内存,于是tcp_listen()就会收回原始连接的内存,而重新分配一个较小内存块供处于监听状态的连接使用. 二 t ...

  9. Ambari 自定义服务集成原理介绍

    之前,在 github 上开源了 ambari-Kylin 项目,可离线部署,支持 hdp 2.6+ 及 hdp 3.0+ .github 地址为:https://github.com/8418090 ...

随机推荐

  1. 运行java程序的时候出现Exception in thread "main" java.lang.UnsupportedClassVersionError:

    1 Exception in thread "main" java.lang.UnsupportedClassVersionError: com/test/inherited/In ...

  2. 一大波 Facebook Libra 技术文章来袭

    由于 Libra 具有真正的稳定的可编程货币的特性, Libra 或许可以带来又一波平台红利. 上周我们(深入浅出区块链技术社区)发起了 Libra 技术文档的翻译计划,到这周文档已经翻译了一半.欢迎 ...

  3. window.showModelessDialog传值

    参数传递:1.   要想对话框传递参数,是通过vArguments来进行传递的.类型不限制,对于字符串类型,最大为4096个字符.也可以传递对象,例如:------------------------ ...

  4. SQL回顾

    数据库的本质是一种特殊的文件 数据库是由数据表组成的,数据表是真正存储数据的 数据库客户端-->SQL语句-->数据库服务器-->数据库文件 表与表之间存在关联的数据库称为关系型数据 ...

  5. Ubuntu 安装最新版 (1.12) Golang 并使用 go mod

    wget https://dl.google.com/go/go1.12.4.linux-amd64.tar.gz sudo tar -zxvf go1.12.4.linux-amd64.tar.gz ...

  6. 关于Android Studio3.2新建项目无法运行出现Failed to find Build Tools revision 28.0.3的解决方法

    关于Android Studio3.2新建项目无法运行出现Failed to find Build Tools revision 28.0.3的解决方法 https://blog.csdn.net/h ...

  7. Roberts算子

    https://blog.csdn.net/likezhaobin/article/details/6892176 https://zhuanlan.zhihu.com/p/35032299 Robe ...

  8. CefSharp中文帮助文档

    https://github.com/cefsharp/CefSharp/wiki/CefSharp%E4%B8%AD%E6%96%87%E5%B8%AE%E5%8A%A9%E6%96%87%E6%A ...

  9. Js设置打印缩放

    近期需要优化一个打印项目,因为是固定长度,所以需要缩放打印,记录一下 //获取打印的页面内容 let subOutputRankPrint = document.getElementById('pri ...

  10. Linux 磁盘管理_016

    以5个方面讲解 1. 硬盘 2. 磁盘RAID.LVM等 3. 磁盘分区 4. 磁盘格式化 5. 磁盘挂载后磁盘管理 一.硬盘 硬盘分类 备注 机械硬盘 IDE  SCSI  SATA  SAS 固态 ...