1、YARN的产生背景和架构剖析

1.1、Hadoop MRv1不足

原 MapReduce 框架也称MRv1,它是一个主从式架构。主节点JobTracker负责集群的资源管理和处理Client请求,从节点TaskTracker负责管理资源和执行任务。不仅仅存在JobTracker的SPOF问题,而且JobTracker的负载非常高,集群的资源管理也非常粗暴不合理
MRv1不足.png
1、单点故障,可靠性低 : JobTracker采用了Master/Slave架构,是集群事务的集中处理点,存在单点故障
2、单点瓶颈,扩展性能差 : JobTracker 需要完成的任务太多,JobTracker兼顾资源管理和作业控制跟踪功能,启动失败或延迟的任务,记录任务的执行状态,维护计数器;压力大,成为系统瓶颈
3、资源管理和任务执行强耦合 : 在TaskTracker端,用Map/Reduce Task作为资源的标识过于简单,没有考虑到CPU、内存等资源情况,当把两个需要消耗大内存的Task调度在一起,很容易出现OOM
4、资源利用率低 : 基于槽位的资源分配模型,槽位是一种粗粒度的资源划分单位,通常一个任务不会用完一个槽位的资源,hadoop1把资源强制划分为Map/Reduce两个Slot,当只有MapTask时,Reduce Slot不能用;
当只有Reduce Task时,Map Slot不能用,容易造成资源利用不足
5、不支持多种分布式计算框架

1.2、Hadoop YARN架构演进

从 Hadoop2.x开始,Hadoop的框架发生了变化;将原来的MapReduce(资源的管理调度和数据的处理计算)集群一分为二 : MapReduce和YARN
MapReduce : 仅仅是一套用来编写分布式计算应用的API
YARN : 是一个Master/Slave框架的分布式集群,用来进行集群的资源管理和调度工作,提供了Job调度规范,除了能运行MapReduce应用程序之外,还可以支持Flink、Spark等分布式计算框架

这样拆分的目的,大大的提高了Hadoop平台的通用性,逐渐演变成一个大数据基础平台,甚至可以理解成用来解决大数据问题的分布式操作系统
Hadoop1.x vs Hadoop 2.x.png
Hadoop2.x分为四部分 : common、hdfs、mapreduce、yarn

1.3、Hadoop YARN概述

YARN,Yet Another Resource Negotiator,是 Hadoop2.x版本中的一个新特性,它为上层应用提供统一的资源管理和调度,它的引入为集群在利用率、资源统一管理和数据共享等方面带来了巨大的好处。
它的出现其实是为了解决第一代MapReduce编程框架的不足,提高集群环境下的资源的利用率,这种资源包括内存,磁盘,网络,IO等。Hadoop2.x版本中重新设计的这个YARN集群,具有更好的扩展性,可用性,
可靠性,向后兼容性,一级能支持除MapReduce以外的更多的分布式计算框架。YARN负责将系统资源分配给在Hadoop集群中运行的各个应用程序,并调度要在不同集群节点上执行的任务。它相当于一个分布式的
操作系统平台,而MapReduce等运算程序相当于运行于操作系统之上的应用程序

YARN的核心特点 :
1、YARN并不清楚用户提交的程序的运行机制,只是提供了一套资源管理和调度规范
2、YARN只提供运算资源的调度(用户程序向YARN申请资源,YARN就负责分配资源,具体的计算执行逻辑完全由用户程序决定)
3、YARN是一个Master/Slave的主从架构,依靠Zookeeper实现HA,主节点叫做ResourceManager,从节点叫做NodeManager
4、YARN被设计成一个通用的资源管理和作业调度平台,Spark、Flink等运算框架都可以整合在YARN上运行,只要满足YARN规范的资源请求即可

1.4、Hadoop YARN(MRv2)优势

YARN/MRv2最基本的想法是将原JobTracker主要的资源管理和Job调度/监控功能分开作为两个单独的守护进程。有一个全局的ResourceManager(RM)和每个Application有一个ApplicationMaster(AM),Applicatio
相当于MapReduce Job或者DAG Jobs。ResourceManager和NodeManager(NM)组成了基本的数据计算框架。ResourceManager协调集群的资源利用,任何Client或者运行的ApplicationMaster想要运行Job或者Task都得向RM申请一定的资源。ApplicationMaster是一个框架特殊的库,对于MapReduce框架而言有它自己的AM视线,用户也可以实现自己的AM,在运行的时候,AM会与NM一起来启动和监控Tasks
Hadoop YARN(MRv2)优势.png
1、极大减少了JobTracker的资源消耗。每个应用程序的ApplicationMaster都分布在分布式整个集群的所有NodeManager中了
2、YARN中的ApplicationMaster只是一个规范。用户可以把自己的分布式计算应用程序部署到AYRN上运行,只要满足ApplicationMaster的规范
3、YARN中的Container的资源抽象比Slot更合理。老版本的Slot分为MapSlot和ReduceSlot,不能混合使用,资源利用率低
4、使用Zookeeper解决RM的SPOF问题。老版本的JobTracker是存在SPOF的问题的

2、YARN核心组件功能特性分析

2.1、YARN Client

YARN Client提交Application到ResourceManager,它会首先创建一个Application上下文对象,并设置ApplicationMaster必须的资源请求信息,然后提交到ResourceManager。YARN Client也可以与ResourceManager通信,获取到一个已经提交并运行的Application的状态信息等

2.2、ResourceManager

管理者(主节点 : 做管理工作) + 工作节点 (提供计算或者存储资源的,用来解决实际问题的)
ResourceManager是一个全局的资源管理器,集群只有一个,有SPOF问题,可以通过Zookeeper实现HA机制,它主要负责整个系统的资源管理和分配,相应用户提交的不同类型应用程序的解析、调度、监控等工作,启动和监控ApplicationMaster,监控NodeManager等

整体职责解析:

  • 处理客户端请求
  • 启动和监控ApplicationMaster
  • 监控NodeManager
  • 负责资源的分配与调度

内部组成结构如下 :
ResourceManager内部组件.png

所有的这些Service都会经历三个步骤 :
1、Service实例的创建,创建好了之后,放在CompositeService的serviceList这个成员变量集合中
2、然后遍历这个serviceList集合,取出每个service的serviceInit()方法
3、然后遍历这个service集合,取出每个service调用serviceStart()方法

关于上述图中的ResourceManager的主要成员的工作职责解析
1、用户交互模块
ClientService : 是为普通用户提供的服务,它会处理来自客户端的各种RPC请求,比如说提交应用程序、获取应用程序运行状态等

AdminService : YARN 为管理员提供了一套独立的服务接口,以防止大量的普通用户请求使用管理员发送的管理命令饿死,管理员可以通过这些接口管理集群,比如说动态更新节点列表、更新ACL列表、更新队列信息(yarn rmadmin -refreshQueues)等
2、NodeManager管理
NMLivenessMonitor : 监控NM是否活着,如果一个NodeManager在一定时间(默认10min)内未汇报心跳信息,则认为它死掉了,会将其从集群中移除

NodesListManager : 维护正常节点和异常节点列表,管理exclude(类似黑名单)和include(类似白名单)节点列表,这两个列表均是在配置文件中设置的,可以动态加载(yarn rmadmin -refreshNodes)

ResourceTrackerService : 出来来自NodeManager的请求,主要包括两种请求 : 注册和心跳。其中,注册是NodeManager启动时发生的行为,请求包中包含节点ID,可用的资源上限等信息,而心跳是周期性行为,包含各个Container运行状态,运行的Application列表、节点健康状态。而ResourceTrackerService则为NM返回待释放的Container列表、Application列表等

3、ApplicationMaster管理
AMLivenessMonitor : 管理AM是否活着,如果一个ApplicationMaster在一定时间(默认10分钟)内未汇报心跳信息,则认为它死掉了,它上面所有正在运行的Container将被认为死亡,AM本身也会被重新分配到另外一个节点上(用户可指定每个ApplicationMaster的尝试次数,默认是1次)执行

ApplicationMasterLauncher : 与NodeManager通信,要求它为某个应用程序启动ApplicationMaster

ApplicationMasterService : 处理来自 ApplicationMaster的请求,主要包括两种请求 : 注册和心跳。其中,注册是ApplicationMaster启动时发生的行为,包含请求包中包含的所在节点,RPC端口号和tracking URL等信息;而心跳是周期性行为,包含请求资源的类型描述、待释放的Container列表等,而AMS则为之返回新分配的Container、失败的Container等信息

4、Application管理
ApplicationACLsManager : 管理应用程序访问权限,包含两部分权限 : 查看和修改,查看主要指查看应用程序的基本信息,而修改主要是修改应用程序优先级、杀死应用程序等

RMAppManager : 管理应用程序的启动和关闭

ContainerAllocationExpire : YARN不允许AM获得Container后场时间不对其使用,因为这样会降低整个集群的利用率。当AM收到RM新分配的一个Container后,必须在一定时间(默认是10min)内在对应的NM上启动该Container,否则,RM会收回该Container

2.3、ApplicationMaster

应用程序管理器ApplicationMaster负责管理整个系统重所有应用程序,包括应用程序提交、与调度器协商资源以启动MRAppMaster、监控MRAppMaster运行状态并在失败时重新启动它等

整体职责解析 :

  • 每个运行在YARN内部的Application都会启动一个ApplicationMaster,负责向ResourceManager注册Application和申请Container
  • ApplicationMaster就是运行在YARN集群中的NodeManager中,相较于MRv1,不会对ResourceManager造成较大的负担
  • 负责整个应用程序的管理,跟NodeManager通信、启动或者停止Task,监控/收集task执行进度结果,或者进行Task的容错

注意ResourceManager、ApplicationMaster、Task之间的关系

  • 客户端提交Job到ResourceManager,ResourceManager会为该Job启动一个ApplicationMaster来负责这个Job中的所有的Task的执行,所以ResourceManager负责管理ApplicationMaster
  • 启动ApplicationMaster专门负责一个Job的所有Task的启动、执行、生命周期管理、状态跟踪、容错等等

2.4、MRAppMaster

MRAppMaster 就是MapReduce的一个Application应用程序在YARN之上的ApplicationMaster
MRAppMaster负责管理MapReduce作业的声明周期,当客户端提交一个MapReduce Job到YARN的时候,ResourceManager会指派一个NodeManager来启动一个MRAppMaster主程序,来主持这个MapReduce Job的所有Task的执行

整体流程.png
步骤1 : 用户向YARN中提交应用程序,其中包括ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等
步骤2 : ResourceManager为该应用程序分配第一个Container,并与之对应的NodeManager通信,要求它在这个Container中启动应用程序的ApplicationMaster
步骤3 : ApplicationMaster首先向ResourceManager注册,这样用户可以直接通过ResourceManager查看应用程序的运行状态,然后它将为各个任务申请资源,并监控
它的运行状态,直到运行结束,即重复步骤4-7
步骤4 : ApplicationMaster采用轮训的方式通过RPC协议向ResourceManager申请资源和领取资源
步骤5 : 一旦ApplicationMaster申请到资源后,便于对应的NodeManager通信,要求它启动任务
步骤6 : NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序)后,将任务启动命令写到一个脚本中,并通过运行脚本启动任务
步骤7 : 各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败重启任务。
在应用程序运行过程中,用户可随时通过RPC向ApplicationMaster查询应用程序的当前运行状态
步骤8 : 应用程序运行完成后,ApplicationMaster向ResourceManager注销并关闭自己

2.5、Scheduler

YARN的资源调度服务 : 根据应用程序需要的资源请以及集群的资源情况,为应用程序分配对应的资源,他不会关心你申请到的Container资源去做什么。调度其就是根据容量、队列一些限制条件,将系统中的资源分配给各个正在运行的应用程序,调度器是一个纯调度器,就是它只管资源分配,不参与具体应用程序相关的工作

YARN内部有3中资源调度策略的视线 : FIFOScheduler、FairScheduler、CapacityScheduler,其中默认实现为CapacityScheduler

分类 :

  • FIFO Scheduler : 先进先出,不考虑应用程序本身的优先级和资源使用情况
  • Capacity Scheduler : 将资源分成队列,共享集群资源但需要保证队列的最小资源使用需求
    A队列,10%
    B队列,20%
    C队列,70%

      C1部门 : 40%
      C2部门 : 60%
  • Fair Scheduler : 公平的将资源分给应用,保证应用使用的资源是均衡的

CapacityScheduler 实现了资源更加细粒度的分配,可以设置多级队列,每个队列都有一定的容量,即对队列设置资源上限和下限,然后对每一级队列分别再采用合适的调度策略(如果FIFO)进行调度

2.6、NodeManager

NodeManager是YARN集群当中真正资源的提供者,是真正执行应用程序的容器的提供者,监控应用程序的资源使用情况(CPU、内存、磁盘、网络),并通过心跳向集群资源调度器ResourceManager进行汇报以及更新自己的
健康状态。同时其他也会监督Container的生命周期管理,监控每个Container的资源使用(内存、CPU等)情况,追踪节点健康状况,管理日志和不同应用程序用到的附属服务(auxiliary service)

整体职责解析 :

  • 管理自身的资源
  • 处理来自ResourceManager的命令
  • 处理来自ApplicationMaster的命令
    NodeManager内部组件.png

所有的这些Service都会经历三个步骤 :

  • Service实例的创建,创建好了之后,放在CompositeService的serviceList这个成员变量集合中
  • 然后遍历这个serviceList集合,取出每个service调用serviceInit()方法
  • 然后遍历这个serviceList集合,取出每个service调用serviceStart()方法

2.7、Container

Flink Slot(配置 : 决定每个从节点被抽象成多少个Slot) = YARN Container 逻辑资源管理单位(默认每个Container用多少资源)

Container容器是一个抽象出来的逻辑资源单位。Container容器是由ResourceManager Scheduler服务动态分配的资源构成,它包括了该节点上的一定量CPU、内存、磁盘、网络等信息,MapReduce程序的所有Task都是在一个容器执行完成的,容器的大小是可以动态调整的

一个NodeManager节点会运行多个Container,单一个Container不会跨节点。任何一个Job或Application必须运行在一个或多个Container中,在YARN框架中,ResourceManager只负责告诉ApplicationMaster哪些Containers可以用,ApplicationMaster还需要去找NodeManager请求分配具体的Container

3、RPC基础概述

RPC(Remote Procudure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议
RPC是指远程过程调用,也就是说两台服务器A,B,一个应用程序部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在同一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据

在实现RPC的时候,需要考虑的一些经典的问题 : 通讯、寻址、序列化、粘包拆包等
Hadoop作为一款成熟稳定,功能复杂强大的分布式系统,底层必然涉及到大量组件之间的相互通信。一款高性能,可扩展,可维护的RPC网络通信框架,必然是Hadoop大厦之根基。所以,现行的Hadoop项目的结构 :

  • Hadoop Common : RPC网络通信框架,各种工具包等
  • Hadoop HDFS : 分布式文件系统(C/S结构的主从架构 分布式系统)
  • Hadoop MapReduce : 分布式应用程序编程框架
  • Hadoop YARN : 分布式资源调度系统(C/S结构的逐层架构 分布式系统)

4、YARN RPC实例解读

Hadoop RPC框架中的序列化机制实现有两种 :

  • Avro Writable接口实现,简单易懂
  • Google Protobuf跨语言实现,高扩展,高效率

4.1、YARN RPC Writable 实例案例

定义 UserService 接口 :

/**
 * 协议接口
 */
public interface UserService {

    long versionID = 1L;
    String say(String name);
}

定义 UserService 实现类 UserServiceImpl :

/**
 * 协议服务组件
 */
public class UserServiceImpl implements UserService {

    @Override
    public String say(String name) {
        return "Hi " + name;
    }
}

定义 RpcServer 服务端 :

/**
 * 默认走的是Writable的avro
 */
public class RpcServer {

    public static void main(String[] args) throws Exception {
        /**
         * 构建一个 RPC Server端
         * 服务端,提供了一个 UserService 协议的 UserServiceImpl 服务实现
         */
        RPC.Server server = new RPC.Builder(new Configuration())
                .setProtocol(UserService.class)
                .setInstance(new UserServiceImpl())
                .setBindAddress("localhost")
                .setPort(8001)
                .setNumHandlers(1)
                .build();
        // RPC server启动
        server.start();

        System.out.println("RpcServer Stared......");
    }
}

定义 RpcClient 客户端 :

public class RpcClient {

    public static void main(String[] args) throws Exception {
        // 获取了服务端中暴露了的服务协议的一个代理
        // 客户端通过这个代理可以调用服务端的方法进行逻辑处理
        UserService userService = RPC.getProxy(UserService.class,
                UserService.versionID,
                new InetSocketAddress("localhost", 8001),
                new Configuration());
        // 在客户端调用老服务端的代码执行,真正的代码执行是在服务端的
        String result = userService.say("zhangsan");
        System.out.println("result : " + result);

    }
}

4.2、YARN RPC Protobuf 实例案例

Hadoop RPC Protobuf 实例图解析.png

MyResourceTrackerMessage.proto,定义请求和响应体 :

syntax = "proto2";
option java_package="com.journey.springboot.hadoop.protobuf.proto";
option java_outer_classname="MyResourceTrackerMessage";
option java_generic_services=true;
option java_generate_equals_and_hash=true;

message MyRegisterNodeManagerRequestProto{
  required string hostname=1;
  required int32 cpu=2;
  required int32 memory=3;
}

message MyRegisterNodeManagerResponseProto{
  required string flag=1;
}

// 编译命令 protoc --proto_path=./ --java_out ../../../../../../  MyResourceTrackerMessage.proto

MyResourceTrackerService.proto,定义 registerNodeManager 接口

syntax = "proto2";
option java_package="com.journey.springboot.hadoop.protobuf.proto";
option java_outer_classname="MyResourceTracker";
option java_generic_services=true;
option java_generate_equals_and_hash=true;

import "MyResourceTrackerMessage.proto";

service MyResourceTrackerService{
  rpc registerNodeManager(MyRegisterNodeManagerRequestProto) returns (MyRegisterNodeManagerResponseProto);
}

// ../../../../../ 其实对应的上面 com.journey.springboot.hadoop.proto的包的根路径层级
// protoc --proto_path=./ --java_out ../../../../../../  MyResourceTrackerService.proto

注意 : 这时候会在 proto 包中生成对应的两个.java文件
image.png

server 如下 :

定义 MyResourceTracker 接口 :

/**
 * 定义一个标准的接口,用来注册nodemanager信息
 * 其中request和response引用了,自动生成的MyResourceTrackerMessage中的内部类MyRegisterNodeManagerResponseProto
 * 和MyRegisterNodeManagerRequestProto
 *
 * 其实这里的作用就是使用了protobuf生成类,做一个标准的接口
 */
public interface MyResourceTracker {
    MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto registerNodeManager(MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto registerNodeManagerRequestProto);
}

定义 MyResourceTracker接口实现类 MyResourceTrackerService :

/**
 * 针对 MyResourceTracker 接口的实现类
 * 这里相当于是ResourceManager服务端的接口实现,接收客户端的NodeManager资源上报
 *
 * 这里做一个标准接口的具体实现
 */
public class MyResourceTrackerService implements MyResourceTracker {

    @Override
    public MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto registerNodeManager(MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto request) {
        // 接收客户端的请求
        String hostname = request.getHostname();
        int cpu = request.getCpu();
        int memory = request.getMemory();
        System.out.println("NodeManager的注册信息 : hostname = " + hostname + ", cpu = " + cpu + ", memory =" + memory);

        // 封装响应
        MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto.Builder builder = MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto.newBuilder();
        builder.setFlag("true");
        MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto response = builder.build();
        return response;
    }
}

定义 MyResourceTrackerPB 接口 :

/**
 * 这里是对hadoop rpc protobuf协议接口的定义
 */
@ProtocolInfo(protocolName = "com.journey.springboot.hadoop.protobuf.server.MyResourceTrackerPB", protocolVersion = 1)
public interface MyResourceTrackerPB extends MyResourceTracker.MyResourceTrackerService.BlockingInterface {
}

定义 MyResourceTrackerPB 接口实现类 MyResourceTrackerServiceSidePB :

/**
 * 这里是对hadoop rpc protobuf协议接口的实现
 */
public class MyResourceTrackerServiceSidePB implements MyResourceTrackerPB {
    final private MyResourceTracker server;

    public MyResourceTrackerServiceSidePB(MyResourceTracker server) {
        this.server = server;
    }

    @Override
    public MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto registerNodeManager(RpcController controller, MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto request) throws ServiceException {
        return this.server.registerNodeManager(request);
    }
}

定义 ProtobufServer :

public class ProtobufServer {

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        String hostname = "localhost";
        int port = 9998;

        // 设置引擎 ProtobufRpcEngine + WritableRpcEngine
        RPC.setProtocolEngine(conf, MyResourceTrackerPB.class, ProtobufRpcEngine.class);

        RPC.Server server = new RPC.Builder(conf)
                .setProtocol(MyResourceTrackerPB.class)
                .setInstance((BlockingService)MyResourceTracker.MyResourceTrackerService.newReflectiveBlockingService(
                        new MyResourceTrackerServiceSidePB(new MyResourceTrackerService())))
                .setBindAddress(hostname)
                .setPort(port)
                .setNumHandlers(1)
                .setVerbose(true)
                .build();
        server.start();
    }
}

定义 ProtobufClient :

public class ProtobufClient {

    public static void main(String[] args) throws Exception {
        // 设置RPC引擎为 ProtobufRpcEngine
        Configuration conf = new Configuration();
        String hostname = "localhost";
        int port = 9998;

        RPC.setProtocolEngine(conf, MyResourceTrackerPB.class, ProtobufRpcEngine.class);

        // 获取代理
        MyResourceTrackerPB protocolProxy = RPC.getProxy(MyResourceTrackerPB.class, 1, new InetSocketAddress(hostname, port), conf);

        MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto.Builder builder =
                MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto
                .newBuilder();
        MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto requestProto =
                builder.setHostname("bigdata01")
                .setCpu(1)
                .setMemory(10)
                .build();
        MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto response = null;

        try {
            response = protocolProxy.registerNodeManager(null, requestProto);
        } catch (Exception e) {
            e.printStackTrace();
        }

        String flag = response.getFlag();
        System.out.println("最终注册结果 : flag = " + flag);
    }
}

5、YARN RPC网络通信框架剖析

Hadoop 1.x版本使用默认实现的Writable协议作为RPC协议,而在Hadoop 2.x版本,重写了RPC框架,改成默认使用Protobuf协议作为Hadoop默认RPC通信协议。在YARN中,任何两个需要相互通信的组件之间仅有一个RPC协议,而对于任何一个RPC协议,通信双方有一端是Client,另一端为Server,且Clieng总是主动连接Server的

13,Haoop RPC Reactor.png
YARN RPC服务端的工作大致可以分为四个阶段 :

第一阶段 : Server初始化和启动
在Server初始化的时候,会初始化Listener组件(内部启动了一个AcceptSelector绑定了响应的端口,用来处理客户端的OP_ACCEPT事件),内部还初始化了一组Reader线程,其实就是启动了ReaderSelector,用来处理OP_READ事件。还启动一个Responder线程来处理响应

在Server调用start()方法启动的时候,启动Listener线程和Responder线程。然后还初始化了一组Handler线程,专门用来处理存储在callQueue的RpcCall请求

第二阶段 : 接收请求,封装RpcCall
代码入口就是 : Listener的run()方法

接收来自各个客户端的RPC请求,并将他们封装成RpcCall类放到一个共享队列callQueue中,改过程可以分为建立连接和接受请求两个阶段,分别由Listener和Reader两种线程完成。具体机制 : 首先Listener接受客户端的请求,然后通过轮训的方式从Reader中获取一个线程用来处理OP_READ事件读取RPC请求数据,如果对应客户端有数据过来,则该Reader负责读取数据,然后封装成RpcCall加入到callQueue队列中等待Handler来执行下一步

第三阶段 : 处理RpcCall请求
Handler从callQueue中获取RpcCall来执行处理,并执行对应的Rpc函数调用,将得到的结果返回给客户端

第四阶段 : 返回结果
在Server内部只有一个Responder线程。它内部的WriteSelector负责监听OP_WRITE事件。如果Responder并不能一次性将结果写出去,则注册OP_WRITE事件分多次异步写

6、YARN RPC源码分析

6.1、RPC 客户端源码分析

Client的定义,org.apache.hadoop.ipc.RPC#getProtocolProxy(...)

public static <T> ProtocolProxy<T> getProtocolProxy(Class<T> protocol,
                                long clientVersion,
                                InetSocketAddress addr,
                                UserGroupInformation ticket,
                                Configuration conf,
                                SocketFactory factory,
                                int rpcTimeout,
                                RetryPolicy connectionRetryPolicy,
                                AtomicBoolean fallbackToSimpleAuth)
   throws IOException {
if (UserGroupInformation.isSecurityEnabled()) {
  SaslRpcServer.init(conf);
}
 // TODO getProtocolEngine 是获取是WritableRpcEngine或者ProtobufRpcEngine
 // TODO 然后调用getProxy,有具体的实现
return getProtocolEngine(protocol, conf).getProxy(protocol, clientVersion,
    addr, ticket, conf, factory, rpcTimeout, connectionRetryPolicy,
    fallbackToSimpleAuth, null);
}

org.apache.hadoop.ipc.ProtobufRpcEngine2#getProxy(...)
public <T> ProtocolProxy<T> getProxy(Class<T> protocol, long clientVersion,
      InetSocketAddress addr, UserGroupInformation ticket, Configuration conf,
      SocketFactory factory, int rpcTimeout, RetryPolicy connectionRetryPolicy,
      AtomicBoolean fallbackToSimpleAuth, AlignmentContext alignmentContext)
      throws IOException {

// TODO 实例化一个Invoker
final Invoker invoker = new Invoker(protocol, addr, ticket, conf, factory,
    rpcTimeout, connectionRetryPolicy, fallbackToSimpleAuth,
    alignmentContext);

// TODO 构建一个ProtocolProxy对象返回
return new ProtocolProxy<T>(protocol, (T) Proxy.newProxyInstance(
    protocol.getClassLoader(), new Class[]{protocol}, invoker), false);
}

org.apache.hadoop.ipc.ProtobufRpcEngine2.Invoker#Invoker(...),Invoker是JDK 动态代理的封装
protected Invoker(Class<?> protocol, InetSocketAddress addr,
        UserGroupInformation ticket, Configuration conf, SocketFactory factory,
        int rpcTimeout, RetryPolicy connectionRetryPolicy,
        AtomicBoolean fallbackToSimpleAuth, AlignmentContext alignmentContext)
        throws IOException {
  // TODO 这里会创建一个Client
  this(protocol, Client.ConnectionId.getConnectionId(
      addr, protocol, ticket, rpcTimeout, connectionRetryPolicy, conf),
      conf, factory);
  this.fallbackToSimpleAuth = fallbackToSimpleAuth;
  this.alignmentContext = alignmentContext;
}

protected Invoker(Class<?> protocol, Client.ConnectionId connId,
        Configuration conf, SocketFactory factory) {
      this.remoteId = connId;
  // TODO 创建一个RPC的客户端
  this.client = CLIENTS.getClient(conf, factory, RpcWritable.Buffer.class);
  this.protocolName = RPC.getProtocolName(protocol);
  this.clientProtocolVersion = RPC
      .getProtocolVersion(protocol);
}

public synchronized Client getClient(Configuration conf,
      SocketFactory factory, Class<? extends Writable> valueClass) {
// Construct & cache client.  The configuration is only used for timeout,
// and Clients have connection pools.  So we can either (a) lose some
// connection pooling and leak sockets, or (b) use the same timeout for all
// configurations.  Since the IPC is usually intended globally, not
// per-job, we choose (a).
Client client = clients.get(factory);
if (client == null) {
  // TODO 真正创建一个Client
  client = new Client(valueClass, conf, factory);
  // TODO 放入缓存中
  clients.put(factory, client);
} else {
  client.incCount();
}
if (Client.LOG.isDebugEnabled()) {
  Client.LOG.debug("getting client out of cache: " + client);
}
return client;
}

当客户端调用RPC方法发起RPC请求的时候,Invoker的invoke(...)方法就会被调用,内部就是调用Client的call方法来发起RPC请求的

org.apache.hadoop.ipc.ProtobufRpcEngine2.Invoker#invoke

// TODO 会真正调用invoke方法,因为是JDK的动态代理,所以这里是核心的客户端调用会走这里
public Message invoke(Object proxy, final Method method, Object[] args)
        throws ServiceException {
     
      ......
      final Message theRequest = (Message) args[1];
      final RpcWritable.Buffer val;
      try {
        // TODO 重点,这里是真正方法的调用,调用的是client.call方法
        val = (RpcWritable.Buffer) client.call(RPC.RpcKind.RPC_PROTOCOL_BUFFER,
            constructRpcRequest(method, theRequest), remoteId,
            fallbackToSimpleAuth, alignmentContext);

      } catch (Throwable e) {
        if (LOG.isTraceEnabled()) {
          LOG.trace(Thread.currentThread().getId() + ": Exception <- " +
              remoteId + ": " + method.getName() +
                " {" + e + "}");
        }
        if (traceScope != null) {
          traceScope.addTimelineAnnotation("Call got exception: " +
              e.toString());
        }
        throw new ServiceException(e);
      } finally {
        if (traceScope != null) {
          traceScope.close();
        }
      }

      ......
    }
}

// TODO 创建一个RPC请求对象
// TODO 对于ProtobufRpcEngine2来说,Call是一个RpcProtobufRequest
Writable call(RPC.RpcKind rpcKind, Writable rpcRequest,
      ConnectionId remoteId, int serviceClass,
      AtomicBoolean fallbackToSimpleAuth, AlignmentContext alignmentContext)
      throws IOException {

    // TODO 1、创建一个Call,封装了rpcKind和rpcRequest
    final Call call = createCall(rpcKind, rpcRequest);
    call.setAlignmentContext(alignmentContext);

    // TODO 2、获取一个链接,初始化连接的各种参数
    final Connection connection = getConnection(remoteId, call, serviceClass,
        fallbackToSimpleAuth);

    try {
      checkAsyncCall();
      try {
        // TODO 3、发送请求
        connection.sendRpcRequest(call);                 // send the rpc request
      } catch (RejectedExecutionException e) {
        throw new IOException("connection has been closed", e);
      } catch (InterruptedException ie) {
        Thread.currentThread().interrupt();
        IOException ioe = new InterruptedIOException(
            "Interrupted waiting to send RPC request to server");
        ioe.initCause(ie);
        throw ioe;
      }
    } catch(Exception e) {
      if (isAsynchronousMode()) {
        releaseAsyncCall();
      }
      throw e;
    }

    ......
}


// 进入getConnection方法
private Connection getConnection(ConnectionId remoteId,
      Call call, int serviceClass, AtomicBoolean fallbackToSimpleAuth)
      throws IOException {
    ......

    Connection connection;
    /* we could avoid this allocation for each RPC by having a  
     * connectionsId object and with set() method. We need to manage the
     * refs for keys in HashMap properly. For now its ok.
     */
    while (true) {
      synchronized (putLock) { // synchronized to avoid put after stop
        if (!running.get()) {
          throw new IOException("Failed to get connection for " + remoteId
              + ", " + call + ": " + this + " is already stopped");
        }
        // TODO 创建一个链接,如果没有则创建,如果有,复用之前的Connection
        connection = connections.computeIfAbsent(remoteId,
            id -> new Connection(id, serviceClass, removeMethod));
      }

      // TODO 将RPC请求加入队列
      if (connection.addCall(call)) {
        break;
      } else {
        // This connection is closed, should be removed. But other thread could
        // have already known this closedConnection, and replace it with a new
        // connection. So we should call conditional remove to make sure we only
        // remove this closedConnection.
        removeMethod.accept(connection);
      }
    }

    // If the server happens to be slow, the method below will take longer to
    // establish a connection.
    // TODO 建立网络链接,初始化输入流输出流,调用start()方法进入工作状态
    connection.setupIOstreams(fallbackToSimpleAuth);
    return connection;
  }

// TODO 进入 setupIOstreams 方法
private synchronized void setupIOstreams(
    AtomicBoolean fallbackToSimpleAuth) {
    ......
    while (true) {
      // TODO 创建客户端,建立网络连接
      setupConnection(ticket);

      // TODO 构建网络输入输出流
      ipcStreams = new IpcStreams(socket, maxResponseLength);
      // TODO 写出请求 Header : 总共7个字节的信息
      writeConnectionHeader(ipcStreams);
      
      // 心跳输入流的设置
      if (doPing) {
        ipcStreams.setInputStream(new PingInputStream(ipcStreams.in));
      }

      // TOOD 将链接上下文信息,写出给对方
      writeConnectionContext(remoteId, authMethod);

      // TODO 更新最近一次活跃的时间
      touch();

      // TODO 进入工作状态
      start();
      return;
    }
  } catch (Throwable t) {
    if (t instanceof IOException) {
      markClosed((IOException)t);
    } else {
      markClosed(new IOException("Couldn't set up IO streams: " + t, t));
    }
    close();
  } finally {
    connectingThread.set(null);
  }
}


// TODO start之后,会将Connection线程启动,会调用里面的run方法,核心其实就是接收服务端的响应请求
@Override
public void run() {
  if (LOG.isDebugEnabled())
    LOG.debug(getName() + ": starting, having connections " 
        + connections.size());

  try {
    while (waitForWork()) {//wait here for work - read or close connection
      // TODO 用于处理接收请求
      receiveRpcResponse();
    }
  } catch (Throwable t) {
    // This truly is unexpected, since we catch IOException in receiveResponse
    // -- this is only to be really sure that we don't leave a client hanging
    // forever.
    LOG.warn("Unexpected error reading responses on connection " + this, t);
    markClosed(new IOException("Error reading responses", t));
  }
  
  close();
  
  if (LOG.isDebugEnabled())
    LOG.debug(getName() + ": stopped, remaining connections "
        + connections.size());
}

org.apache.hadoop.ipc.Client.Connection#sendRpcRequest,发送RPC Call请求
public void sendRpcRequest(final Call call)
        throws InterruptedException, IOException {
      ......
      // TODO 构建数据报文
      RpcRequestHeaderProto header = ProtoUtil.makeRpcRequestHeader(
          call.rpcKind, OperationProto.RPC_FINAL_PACKET, call.id, call.retry,
          clientId, call.alignmentContext);

      final ResponseBuffer buf = new ResponseBuffer();
      header.writeDelimitedTo(buf);
      RpcWritable.wrap(call.rpcRequest).writeTo(buf);

      synchronized (sendRpcRequestLock) {
        // TODO 通过一个线程来发送这个RpcRequest请求数据
        Future<?> senderFuture = sendParamsExecutor.submit(new Runnable() {
          @Override
          public void run() {
            try {
              synchronized (ipcStreams.out) {
                if (shouldCloseConnection.get()) {
                  return;
                }
                if (LOG.isDebugEnabled()) {
                  LOG.debug(getName() + " sending #" + call.id
                      + " " + call.rpcRequest);
                }
                // RpcRequestHeader + RpcRequest
                // TODO 发送
                ipcStreams.sendRequest(buf.toByteArray());
                ipcStreams.flush();
              }
            } catch (IOException e) {
              // exception at this point would leave the connection in an
              // unrecoverable state (eg half a call left on the wire).
              // So, close the connection, killing any outstanding calls
              markClosed(e);
            } finally {
              //the buffer is just an in-memory buffer, but it is still polite to
              // close early
              IOUtils.closeStream(buf);
            }
          }
        });
      
        try {
          // TODO 同步等待
          senderFuture.get();
        } catch (ExecutionException e) {
          Throwable cause = e.getCause();
          
          // cause should only be a RuntimeException as the Runnable above
          // catches IOException
          if (cause instanceof RuntimeException) {
            throw (RuntimeException) cause;
          } else {
            throw new RuntimeException("unexpected checked exception", cause);
          }
        }
      }
    }
}

6.2、RPC 服务端源码分析

Server的定义 : org.apache.hadoop.ipc.Server。RPC Server的创建的代码入口是 : RPC.getProtocolEngine(...).getServer()
org.apache.hadoop.ipc.RPC.Builder#build

public Server build() throws IOException, HadoopIllegalArgumentException {
  ......
  // TODO ProtocolEngine有两种实现 ,WritableRpcEngine和ProtobufRpcEngine
  // TODO getServer是核心
  return getProtocolEngine(this.protocol, this.conf).getServer(
      this.protocol, this.instance, this.bindAddress, this.port,
      this.numHandlers, this.numReaders, this.queueSizePerHandler,
      this.verbose, this.conf, this.secretManager, this.portRangeConfig,
      this.alignmentContext);
}

org.apache.hadoop.ipc.ProtobufRpcEngine2#getServer,查看Server构造方法
public RPC.Server getServer(Class<?> protocol, Object protocolImpl,
      String bindAddress, int port, int numHandlers, int numReaders,
      int queueSizePerHandler, boolean verbose, Configuration conf,
      SecretManager<? extends TokenIdentifier> secretManager,
      String portRangeConfig, AlignmentContext alignmentContext)
      throws IOException {
    // TODO 实例化一个Server
return new Server(protocol, protocolImpl, conf, bindAddress, port,
    numHandlers, numReaders, queueSizePerHandler, verbose, secretManager,
    portRangeConfig, alignmentContext);
}

protected Server(String bindAddress, int port,
      Class<? extends Writable> rpcRequestClass, int handlerCount,
      int numReaders, int queueSizePerHandler, Configuration conf,
      String serverName, SecretManager<? extends TokenIdentifier> secretManager,
      String portRangeConfig)
    ......
    // TODO 初始化一个Call队列
    this.callQueue = new CallQueueManager<Call>(getQueueClass(prefix, conf),
        getSchedulerClass(prefix, conf),
        getClientBackoffEnable(prefix, conf), maxQueueSize, prefix, conf);

   
    // TODO 创建一个Listener线程 : 内部启动NIO服务端,监听链接请求
    // TODO Listener当中会初始化AcceptSelector和ReaderSelector
    listener = new Listener(port);
    // set the server port to the default listener port.
    this.port = listener.getAddress().getPort();

    // TODO 做链接超时处理,启动定时任务做检查
    connectionManager = new ConnectionManager();
    ....

    // Create the responder here
    // TODO 实例化Responder
    responder = new Responder();
    
    ......
 }


Listener(int port) throws IOException {
      address = new InetSocketAddress(bindAddress, port);
      // Create a new server socket and set to non blocking mode
      // TODO 创建一个新的服务端socket并设置非阻塞模式
      acceptChannel = ServerSocketChannel.open();
      acceptChannel.configureBlocking(false);
      acceptChannel.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddr);

      // Bind the server socket to the local host and port
      bind(acceptChannel.socket(), address, backlogLength, conf, portRangeConfig);
      //Could be an ephemeral port
      this.listenPort = acceptChannel.socket().getLocalPort();
      Thread.currentThread().setName("Listener at " +
          bindAddress + "/" + this.listenPort);
      // create a selector;
      selector= Selector.open();
      readers = new Reader[readThreads];
      for (int i = 0; i < readThreads; i++) {
        // TODO 实例化Reader
        Reader reader = new Reader(
            "Socket Reader #" + (i + 1) + " for port " + port);
        readers[i] = reader;
        reader.start();
      }

      // Register accepts on the server socket with the selector.
      // TODO 注册OP_ACCEPT事件
      acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
      this.setName("IPC Server listener on " + port);
      this.setDaemon(true);
      this.isOnAuxiliaryPort = false;
}

RPC Server的启动代码入口是 : org.apache.hadoop.ipc.Server#start
// TODO 启动服务
public synchronized void start() {
    // TODO responder线程启动
    responder.start();
    // TODO listener线程启动
    listener.start();
    if (auxiliaryListenerMap != null && auxiliaryListenerMap.size() > 0) {
      for (Listener newListener : auxiliaryListenerMap.values()) {
        newListener.start();
      }
    }

    // TODO 实例化 Handler 线程
    handlers = new Handler[handlerCount];

    for (int i = 0; i < handlerCount; i++) {
      handlers[i] = new Handler(i);
      // TODO Handler线程启动
      handlers[i].start();
    }
}

Listener 核心run逻辑 org.apache.hadoop.ipc.Server.Listener.Reader#run :

public void run() {
        LOG.info("Starting " + Thread.currentThread().getName());
    try {
      // TODO 循环执行
      doRunLoop();
    } finally {
      try {
        readSelector.close();
      } catch (IOException ioe) {
        LOG.error("Error closing read selector in " + Thread.currentThread().getName(), ioe);
      }
    }
}

private synchronized void doRunLoop() {
    while (running) {
      SelectionKey key = null;
      try {
        // TODO 注册OP_READ,从pendingConnections中获取刚刚建立连接成功的客户端
        // TODO 注册OP_READ事件,读取数据
        int size = pendingConnections.size();
        for (int i=size; i>0; i--) {
          Connection conn = pendingConnections.take();
          conn.channel.register(readSelector, SelectionKey.OP_READ, conn);
        }

        /**
         * TODO 如果这个iter不为空,意味着 socketChannel 触发了 OP_READ事件,对应的客户端读取数据
         */
        readSelector.select();

        Iterator<SelectionKey> iter = readSelector.selectedKeys().iterator();
        while (iter.hasNext()) {
          key = iter.next();
          iter.remove();
          try {
            // TODO 读取数据
            if (key.isReadable()) {
              doRead(key);
            }
          } catch (CancelledKeyException cke) {
            // something else closed the connection, ex. responder or
            // the listener doing an idle scan.  ignore it and let them
            // clean up.
            LOG.info(Thread.currentThread().getName() +
                ": connection aborted from " + key.attachment());
          }
          key = null;
        }
      }
      .....
    }
}


void doRead(SelectionKey key) throws InterruptedException {
  int count;
  Connection c = (Connection)key.attachment();
  if (c == null) {
    return;  
  }
  c.setLastContact(Time.now());
  
  try {
    /**
     * 1、读取数据
     * 2、封装成RpcCall加入callQueue队列
     */
    count = c.readAndProcess();
  } 
  ......
}


public int readAndProcess() throws IOException, InterruptedException {
  while (!shouldClose()) { // stop if a fatal response has been sent.
    // dataLengthBuffer is used to read "hrpc" or the rpc-packet length
    int count = -1;
    if (dataLengthBuffer.remaining() > 0) {
      // TODO 这里是先读取4个字节
      count = channelRead(channel, dataLengthBuffer);       
      if (count < 0 || dataLengthBuffer.remaining() > 0) 
        return count;
    }

    // TODO 如果请求头没有读过,则需要先读取请求头
    if (!connectionHeaderRead) {
      // Every connection is expected to send the header;
      // so far we read "hrpc" of the connection header.
      if (connectionHeaderBuf == null) {
        // for the bytes that follow "hrpc", in the connection header
        connectionHeaderBuf = ByteBuffer.allocate(HEADER_LEN_AFTER_HRPC_PART);
      }
      // TODO 读取请求头
      count = channelRead(channel, connectionHeaderBuf);
      if (count < 0 || connectionHeaderBuf.remaining() > 0) {
        return count;
      }
      int version = connectionHeaderBuf.get(0);
      // TODO we should add handler for service class later
      this.setServiceClass(connectionHeaderBuf.get(1));
      dataLengthBuffer.flip();
      
      // Check if it looks like the user is hitting an IPC port
      // with an HTTP GET - this is a common error, so we can
      // send back a simple string indicating as much.
      if (HTTP_GET_BYTES.equals(dataLengthBuffer)) {
        setupHttpRequestOnIpcPortResponse();
        return -1;
      }

      if(!RpcConstants.HEADER.equals(dataLengthBuffer)) {
        LOG.warn("Incorrect RPC Header length from {}:{} "
            + "expected length: {} got length: {}",
            hostAddress, remotePort, RpcConstants.HEADER, dataLengthBuffer);
        setupBadVersionResponse(version);
        return -1;
      }
      ......
    }
    
    if (data == null) { // just read 4 bytes -  length of RPC packet
      dataLengthBuffer.flip();
      dataLength = dataLengthBuffer.getInt();
      checkDataLength(dataLength);
      // Set buffer for reading EXACTLY the RPC-packet length and no more.
      data = ByteBuffer.allocate(dataLength);
    }
    // Now read the RPC packet
    // TODO 读取rpc请求数据
    count = channelRead(channel, data);
    
    if (data.remaining() == 0) {
      dataLengthBuffer.clear(); // to read length of future rpc packets
      data.flip();
      ByteBuffer requestData = data;
      data = null; // null out in case processOneRpc throws.
      boolean isHeaderRead = connectionContextRead;
      // TODO requestData RPC请求的数据包
      processOneRpc(requestData);
      // the last rpc-request we processed could have simply been the
      // connectionContext; if so continue to read the first RPC.
      if (!isHeaderRead) {
        continue;
      }
    } 
    return count;
  }
  return -1;
}

调用 org.apache.hadoop.ipc.Server.Connection#processOneRpc

private void processRpcRequest(RpcRequestHeaderProto header,
        RpcWritable.Buffer buffer) throws RpcServerException,
        InterruptedException {
  ......
  // TODO rpcRequest是rpc的请求数据
  RpcCall call = new RpcCall(this, header.getCallId(),
      header.getRetryCount(), rpcRequest,
      ProtoUtil.convert(header.getRpcKind()),
      header.getClientId().toByteArray(), traceScope, callerContext);
  ......

  try {
    // TODO 直接放入到callQueue队列中
    internalQueueCall(call);
  } catch (RpcServerException rse) {
    throw rse;
  } catch (IOException ioe) {
    throw new FatalRpcServerException(
        RpcErrorCodeProto.ERROR_RPC_SERVER, ioe);
  }
  ...
}

最终放入到 callQueue 中

org.apache.hadoop.ipc.Server#internalQueueCall(...)
private void internalQueueCall(Call call, boolean blocking)
      throws IOException, InterruptedException {
    try {
      // queue the call, may be blocked if blocking is true.
      // TODO 这里说明是阻塞队列还是非阻塞的方式放入,默认是阻塞的方式放入的
      if (blocking) {
        callQueue.put(call);
      } else {
        callQueue.add(call);
      }
    
    } 
    ......
}


org.apache.hadoop.ipc.Server.Handler#run,消费callQueue中的内容
public void run() {
  .....
  while (running) {
    TraceScope traceScope = null;
    Call call = null;

    try {
      /**
       * 1、Reader读取client发送过来的RPC请求数据包,然后封装成RpcCall对象加入到callQueue队列
       * 2、Handler线程专门负责消费 : callQueue中的消息 : RpcCall
       */
      call = callQueue.take(); // pop the queue; maybe blocked here
      ......
      if (remoteUser != null) {
        remoteUser.doAs(call);
      } else {
        // TODO 真正执行RpcCall请求
        call.run();
      }
    } 
    ......
}


org.apache.hadoop.ipc.Server.RpcCall#run
public Void run() throws Exception {
  ......
  try {
    /**
     * 1、call()就是执行RPC请求
     * 2、value就是RPC请求的返回值
     */
    value = call(
        rpcKind, connection.protocolName, rpcRequest, timestampNanos);
  } catch (Throwable e) {
    populateResponseParamsOnError(e, responseParams);
  }
  if (!isResponseDeferred()) {
    ......

    // TODO 设置RPC处理结果,将PRC处理结果设置到RpcCall中了
    setResponseFields(value, responseParams);
    // TODO 发送响应
    sendResponse();

    deltaNanos = Time.monotonicNowNanos() - startNanos;
    details.set(Timing.RESPONSE, deltaNanos, TimeUnit.NANOSECONDS);
  } else {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Deferring response for callId: " + this.callId);
    }
  }
  return null;
}

call核心,org.apache.hadoop.ipc.ProtobufRpcEngine.Server#processCall
static RpcWritable processCall(RPC.Server server,
        String connectionProtocolName, RpcWritable.Buffer request,
        String methodName, ProtoClassProtoImpl protocolImpl) throws Exception {
      // TODO 获取service
      BlockingService service = (BlockingService) protocolImpl.protocolImpl;
      MethodDescriptor methodDescriptor = service.getDescriptorForType()
          .findMethodByName(methodName);
      if (methodDescriptor == null) {
        String msg = "Unknown method " + methodName + " called on "
                              + connectionProtocolName + " protocol.";
        LOG.warn(msg);
        throw new RpcNoSuchMethodException(msg);
      }
      Message prototype = service.getRequestPrototype(methodDescriptor);
      Message param = request.getValue(prototype);

      Message result;
      Call currentCall = Server.getCurCall().get();
      try {
        server.rpcDetailedMetrics.init(protocolImpl.protocolClass);
        CURRENT_CALL_INFO.set(new CallInfo(server, methodName));
        currentCall.setDetailedMetricsName(methodName);
        /**
         * 真正的RPC调用
         * Service就是对应的Protocol
         *
         * 类似调用 resourceTracker.registerNodeManager()方法
         */
        result = service.callBlockingMethod(methodDescriptor, null, param);
       ......
      } catch (ServiceException e) {
        Exception exception = (Exception) e.getCause();
        currentCall
            .setDetailedMetricsName(exception.getClass().getSimpleName());
        throw (Exception) e.getCause();
      } catch (Exception e) {
        currentCall.setDetailedMetricsName(e.getClass().getSimpleName());
        throw e;
      } finally {
        CURRENT_CALL_INFO.set(null);
      }
      return RpcWritable.wrap(result);
    }
}

void doRespond(RpcCall call) throws IOException {
      synchronized (call.connection.responseQueue) {
    // must only wrap before adding to the responseQueue to prevent
    // postponed responses from being encrypted and sent out of order.
    if (call.connection.useWrap) {
      wrapWithSasl(call);
    }
    // TODO 加入队列
    call.connection.responseQueue.addLast(call);
    // TODO 执行处理
    if (call.connection.responseQueue.size() == 1) {
      processResponse(call.connection.responseQueue, true);
    }
  }
}

sendResponse(); 其实如果能一次性写出去,直接走的Handler线程中调用的Responder中的doResponse()方法(一个Connection,前提就是当前只能有一个返回值要往外写),一旦一次性写不出去就会让Responder线程run的 doAsyncWrite 方法已经异步写

7、YARN集群启动和核心工作机制源码剖析

YARN的核心设计理念是服务化(Service 和 事件驱动(Event + EventHandler)。服务化和事件驱动设计思想的引入,使得YARN具有低耦合、高内聚的特点,各个模块只需完成各自功能,而模块之间则
采用事件联系起来,系统设计简单维护方便。这种编程方式具有异步、并发等特点,更加高效,更适合大型分布式系统
状态转移四元组 : 转移前状态、转移后状态、事件、Transition
假设 : 我们来研究MapReduce On YARN的运行 : 总共10个状态机 大概 60个Transition左右联合完成工作

7.1、YARN Service服务库

对于生命周期较长的对象,YARN采用了基于服务的对象管理模型对其进行管理,该模型主要有以下几个特点 :

  • 将每个被服务化的对象分为4个状态 : NOTINITED(被创建)、INITED(已初始化)、STARTED(已启动)、STOPPED(已停止)
  • 任何服务状态变化都可以触发另外一些动作
  • 可通过组合的方式对任意服务进行组合,以便进行统一管理
public class ResourceManager extends CompositeService implements Recoverable, ResourceManagerMXBean {}
public class NodeManager extends CompositeService implements EventHandler<NodeManagerEvent>, NodeManagerMXBean {}

public class CompositeService extends AbstractService {

  // TODO 服务集合
  private final List<Service> serviceList = new ArrayList<Service>();
}

public abstract class AbstractService implements Service {}

请看 Service 的定义 :

public interface Service extends Closeable {

    public enum STATE {
        NOTINITED(0, "NOTINITED"),
        INITED(1, "INITED"),
        STARTED(2, "STARTED"),
        STOPPED(3, "STOPPED");
    }

    // 服务初始化
    void init(Configuration config);

    // 服务启动
    void start();

    // 服务停止
    void stop();

    // 服务关闭
    void close() throws IOException;
}

service生命周期.png

Service是一个接口,他的抽象子类 : AbstractService对上述三个方法进行了实现 :

public abstract class AbstractService implements Service {
    
    private final String name;

      private final ServiceStateModel stateModel;

    @Override
      public void init(Configuration conf) {
          serviceInit(conf);
    }

    @Override
    public void start() {
        serviceStart();
    }

    @Override
    public void stop() {
        serviceStop();
    }

    protected void serviceInit(Configuration conf) throws Exception {

    }
      
      protected void serviceStart() throws Exception {

    }

    protected void serviceStop() throws Exception {

      }
}

在 AbstractService中,三个成员方法的具体实现,还是由AbstractService的子类CompositeService实现的。重点看CompositeService的定义 :

public class CompositeService extends AbstractService {
    
  // TODO 所有服务
  private final List<Service> serviceList = new ArrayList<Service>();

  // 获取所有子服务
  public List<Service> getServices() {
    synchronized (serviceList) {
      return new ArrayList<Service>(serviceList);
    }
  }

  // 添加子service的方法
  protected void addService(Service service) {
    synchronized (serviceList) {
      serviceList.add(service);
    }
  }

  // 添加子service的方法
  protected boolean addIfService(Object object) {
    if (object instanceof Service) {
      addService((Service) object);
      return true;
    } else {
      return false;
    }
  }

  // 针对所有Service执行init初始化 : 最终调用service的serviceInit()方法
  protected void serviceInit(Configuration conf) throws Exception {
    // 获取当前service组件的所有子service
    List<Service> services = getServices();
    // 遍历每个子service调用init(),执行初始化
    for (Service service : services) {
      service.init(conf);
    }
    super.serviceInit(conf);
  }

  // 针对所有Service执行start初始化 : 最终调用service的serviceStart()方法
  protected void serviceStart(Configuration conf) throws Exception {
    List<Service> services = getServices();
    for (Service service : services) {
      service.start(conf);
    }
    super.serviceStart(conf);
  }
}

YARN中,会有非常多的Service,每个Service的初始化和启动的业务,如果有,就各自做自己的实现,否则使用父类的,在ResourceManager中,包含了很多的service,ResourceManager本身自己也是一个service :
ResourceManager内部组件.png

在NodeManager中也包含了很多的Service,NodeManager自己本身一是一个Service :
NodeManager内部组件.png

7.2、YARN 集群启动ResourceManager启动源码详解

ResourceManager启动核心 :

ResourceManager.main() {
    
    // ResourceManager初始化
    ResourceManager resourceManager = new ResourceManager();

    // 各种服务创建和初始化
    resourceManager.init(conf);

    // 各种服务启动
    resourceManager.start();
}

ResourceManager是Service的子类。看继承结构 :

public class ResourceManager extends CompositeService implements Recoverable, ResourceManagerMXBean {}
public class CompositeService extends AbstractService {}
public abstract class AbstractService implements Service {}

image.png

如果要研究一个Service组件的内部功能,则重点关注它的三个方法 :

  • 构造方法 : 服务创建
  • serviceInit() : 服务初始化
  • serviceStart() : 服务启动

7.2.1、ResourceManager的serviceInit()

首先看ResourceManager的各种Service的创建和初始化 :

protected void serviceInit(Configuration conf) throws Exception { 

    // 生成一个上下文对象
    this.rmContext = new RMContextImpl();
    rmContext.setResourceManager(this);

    // TODO 读取core-site.xml配置
    loadConfigurationXml(YarnConfiguration.CORE_SITE_CONFIGURATION_FILE);

    // TODO 读取 yarn-site.xml配置
    loadConfigurationXml(YarnConfiguration.YARN_SITE_CONFIGURATION_FILE);

    // TODO HA的一些设置和相关服务地址检查,yarn.resourcemanager.ha.enabled=true
    this.rmContext.setHAEnabled(HAUtil.isHAEnabled(this.conf));
    if (this.rmContext.isHAEnabled()) {
      // TODO 必要参数的校验
      HAUtil.verifyAndSetConfiguration(this.conf);
    }

    // TODO 初始化和添加 AsyncDispatcher
    rmDispatcher = setupDispatcher() {
        Dispatcher dispatcher = createDispatcher() {
            new AsyncDispatcher("RM Event dispatcher");
        }
        dispatcher.register(RMFatalEventType.class, new ResourceManager.RMFatalEventDispatcher());
    }
    addIfService(rmDispatcher);
    rmContext.setDispatcher(rmDispatcher);


    // AdminService 为管理员提供了一套独立的服务接口
    adminService = createAdminService();
    addService(adminService);
    rmContext.setRMAdminService(adminService);

    // 如果开启了HA,根据配置,创建相关的选举组件
    if (this.rmContext.isHAEnabled()) {
      // If the RM is configured to use an embedded leader elector,
      // initialize the leader elector.
      // TODO yarn.resourcemanager.ha.automatic-failover.enabled = true
      // TODO yarn.resourcemanager.ha.automatic-failover.embedded = true
      // TODO 如果上一个参数为true表示使用原生的 zookeeper API来执行选举
      // TODO 否则使用 curator框架来实现
      if (HAUtil.isAutomaticFailoverEnabled(conf)
          && HAUtil.isAutomaticFailoverEmbedded(conf)) {
        // TODO 创建选举器 ActiveStandbyElectorBasedElectorService
        EmbeddedElector elector = createEmbeddedElector();
        addIfService(elector);
        rmContext.setLeaderElectorService(elector);
      }
    }

    // TODO 里面创建了很多service实例,都加入到了serviceList集合中
    // TODO 内部创建了一个StandByTransitionRunnable 选举线程
    // TODO ActiveService 是只有 Active RM才会启动的服务
    createAndInitActiveServices(false) {
        activeServices = new RMActiveServices(this);
        activeServices.fromActive = fromActive;
        // TODO 一旦调用了这个RMActiveServices的init方法,就会调用它的 serviceInit() 方法
        activeServices.init(conf);
    }

    // TODO webApp默认端口 8090
    webAppAddress = WebAppUtils.getWebAppBindURL(this.conf,
                      YarnConfiguration.RM_BIND_HOST,
                      WebAppUtils.getRMWebAppURLWithoutScheme(this.conf));


    /**
     * TODO 创建RMApplicationHistoryWriter,然后加入 serviceList
     * 持久化 RMApp、RMAppAttempt、RMContainer的信息
     */
    RMApplicationHistoryWriter rmApplicationHistoryWriter =
        createRMApplicationHistoryWriter();
    addService(rmApplicationHistoryWriter);
    rmContext.setRMApplicationHistoryWriter(rmApplicationHistoryWriter);


    /**
     *  创建 RMTimelineCollectorManager,然后加入 serviceList
     *  默认没有起开
     */
    if (YarnConfiguration.timelineServiceV2Enabled(this.conf)) {
      RMTimelineCollectorManager timelineCollectorManager =
          createRMTimelineCollectorManager();
      addService(timelineCollectorManager);
      rmContext.setRMTimelineCollectorManager(timelineCollectorManager);
    }

    // TODO 创建 SystemMetricsPublisher,然后加入 serviceList
    // TODO 生产系统指标数据
    SystemMetricsPublisher systemMetricsPublisher =
        createSystemMetricsPublisher();
    addIfService(systemMetricsPublisher);
    rmContext.setSystemMetricsPublisher(systemMetricsPublisher);

    /**
     * 初始化各种服务,调用各种service实例的serviceInit()方法
     */
    super.serviceInit(this.conf);
}

7.2.2、ActiveServices详解

核心入口是下面这句话,它的内部创建了一个RMActiveServices对象,并且调用了该实例的init()方法,其实init()方法的内部就是调用serviceInit()方法

protected void createAndInitActiveServices(boolean fromActive) {
    // TODO RMActiveServices这个是重点
    activeServices = new RMActiveServices(this);
    activeServices.fromActive = fromActive;
    // TODO 一旦调用了这个RMActiveServices的init方法,就会调用它的 serviceInit() 方法
    activeServices.init(conf);
}

请看详细实现 : RMActiveServices.serviceInit()

RMActiveServices.serviceInit() {
    
    /**
      * TODO 选举线程
      * 1、之前创建了一个 选举实例
      * 2、此时创建了一个 选举线程
      * 选举实例,会尝试最多3次选举,如果没有成功,则启动这个线程来执行选举
       */
    standByTransitionRunnable = new StandByTransitionRunnable();

    /**
      * TODO RMSecretManagerService
      * RMSecretManagerService 主要提供了一些Token相关服务
    */
    rmSecretManagerService = createRMSecretManagerService();
    addService(rmSecretManagerService);

    /**
     * TODO 过期处理器
     * 监控 Container 是否过期(提交 ApplicationMaster 时检查)
     * 当 AM 收到 RM 新分配的一个 Container之后,必须在一定时间(默认为10min)
     * 内在对应的NM上启动该 Container,否则,RM会回收该 Container
     */
    containerAllocationExpirer = new ContainerAllocationExpirer(rmDispatcher);
    addService(containerAllocationExpirer);
    rmContext.setContainerAllocationExpirer(containerAllocationExpirer);

    /**
     * 监听 ApplicationMaster状态 = AMLivenesssMonitor
     * AM 存活监控,继承自 AbstractLivelinessMonitor,过期
     * 发生时会触发回调函数
     */
    AMLivelinessMonitor amLivelinessMonitor = createAMLivelinessMonitor();
    addService(amLivelinessMonitor);
    rmContext.setAMLivelinessMonitor(amLivelinessMonitor);


    /**
    * 创建一个AMLivelinessMonitor启用监控 AM 的Finish 动作
    */
    AMLivelinessMonitor amFinishingMonitor = createAMLivelinessMonitor();
    addService(amFinishingMonitor);
    rmContext.setAMFinishingMonitor(amFinishingMonitor);

    /**
    * RMApp 生命周期管理,如果一个 RMApp在给定时间里面,没有运行完毕,则会被关闭
    */
    RMAppLifetimeMonitor rmAppLifetimeMonitor = createRMAppLifetimeMonitor();
    addService(rmAppLifetimeMonitor);
    rmContext.setRMAppLifetimeMonitor(rmAppLifetimeMonitor);

    /**
    * RMNode 标签管理服务
    */
    RMNodeLabelsManager nlm = createNodeLabelManager();
    nlm.setRMContext(rmContext);
    addService(nlm);
    rmContext.setNodeLabelManager(nlm);

    /**
    * 属性管理服务
    */
    NodeAttributesManager nam = createNodeAttributesManager();
    addService(nam);
    rmContext.setNodeAttributesManager(nam);


    PlacementConstraintManagerService placementConstraintManager = createPlacementConstraintManager();
    addService(placementConstraintManager);
    rmContext.setPlacementConstraintManager(placementConstraintManager);

    /**
    * 资源概况,资源期
    */
    ResourceProfilesManager resourceProfilesManager = createResourceProfileManager();
    resourceProfilesManager.init(conf);
    rmContext.setResourceProfilesManager(resourceProfilesManager);

    /**
    * MultiNodeSortingManager
    */
    MultiNodeSortingManager<SchedulerNode> multiNodeSortingManager = createMultiNodeSortingManager();
    multiNodeSortingManager.setRMContext(rmContext);
    addService(multiNodeSortingManager);
    rmContext.setMultiNodeSortingManager(multiNodeSortingManager);


    // RMDelegatedNodeLabelsUpdater,默认不启用
    RMDelegatedNodeLabelsUpdater delegatedNodeLabelsUpdater = createRMDelegatedNodeLabelsUpdater();
    if (delegatedNodeLabelsUpdater != null) {
      addService(delegatedNodeLabelsUpdater);
      rmContext.setRMDelegatedNodeLabelsUpdater(delegatedNodeLabelsUpdater);
    }


    /**
    * 如果启动了 recoveryEnabled,则 rmStore 默认实现是 MemoryRMStateStore
    * 可以通过 yarn.resourcemanager.store.class 配置修改
    */
    RMStateStore rmStore = null;
    if (recoveryEnabled) {
      // MemoryRMStateStore
      rmStore = RMStateStoreFactory.getStore(conf);
      boolean isWorkPreservingRecoveryEnabled =
          conf.getBoolean(
            YarnConfiguration.RM_WORK_PRESERVING_RECOVERY_ENABLED,
            YarnConfiguration.DEFAULT_RM_WORK_PRESERVING_RECOVERY_ENABLED);
      rmContext
          .setWorkPreservingRecoveryEnabled(isWorkPreservingRecoveryEnabled);
    } else {
      rmStore = new NullRMStateStore();
    }
    rmStore.setResourceManager(rm);
    rmStore.init(conf);
    rmStore.setRMDispatcher(rmDispatcher);
    rmContext.setStateStore(rmStore);


    /**
    * Node 列表管理器,还用 rmDispatcher 注册了一个 NodesListManagerEventType 事件处理(节点可用/不可用)
    */
    nodesListManager = new NodesListManager(rmContext);
    rmDispatcher.register(NodesListManagerEventType.class, nodesListManager);
    addService(nodesListManager);
    rmContext.setNodesListManager(nodesListManager);


    /**
     * 默认调度器,默认是 CapacityScheduler
     */
    scheduler = createScheduler();
    scheduler.setRMContext(rmContext);
    addIfService(scheduler);
    rmContext.setScheduler(scheduler);

    /**
     * 返回 EventDispatcher
     */
    schedulerDispatcher = createSchedulerEventDispatcher();
    addIfService(schedulerDispatcher);

    /**
     * 用rmDispatcher 注册了一个 SchedulerEventType 事件处理
     * rmDispatcher = AsyncDispatcher
     * 注册方法有两个参数
     * 第一个参数 : 事件类型
     * 第二个参数 : 事件处理器
     * 如果提交一个事件到 AsyncDispatcher 那么这个 AsyncDispatcher 就会找到之前注册的这个事件对应的 EventHandler 来执行事件处理
     * EventHandler有可能是一个状态机,也有可能是一个普通的EventHandler
     */
    rmDispatcher.register(SchedulerEventType.class, schedulerDispatcher);
    rmDispatcher.register(RMAppEventType.class, new ApplicationEventDispatcher(rmContext));
    rmDispatcher.register(RMAppAttemptEventType.class, new ApplicationAttemptEventDispatcher(rmContext));
    rmDispatcher.register(RMNodeEventType.class, new NodeEventDispatcher(rmContext));

    // NodeManager存活监控
    nmLivelinessMonitor = createNMLivelinessMonitor();
    addService(nmLivelinessMonitor);

    // 处理NodeManager的注册和心跳的ResourceTrackerService服务
    resourceTracker = createResourceTrackerService();
    addService(resourceTracker);
    rmContext.setResourceTrackerService(resourceTracker);

    // Jvm暂停监视器
    JvmPauseMonitor pauseMonitor = new JvmPauseMonitor();
    addService(pauseMonitor);
    jvmMetrics.setPauseMonitor(pauseMonitor);


    // Reservation服务初始化
    if (conf.getBoolean(YarnConfiguration.RM_RESERVATION_SYSTEM_ENABLE,
        YarnConfiguration.DEFAULT_RM_RESERVATION_SYSTEM_ENABLE)) {
      reservationSystem = createReservationSystem();
      if (reservationSystem != null) {
        reservationSystem.setRMContext(rmContext);
        addIfService(reservationSystem);
        rmContext.setReservationSystem(reservationSystem);
        LOG.info("Initialized Reservation system");
      }
    }


    /**
     * 用于对所有提交的 ApplicationMaster 进行管理
     * 该组件相应所有来自AM的请求,实现了 ApplicationMasterProtocol协议,这个协议是AM与RM通信的唯一协议
     * 主要包括以下任务 :
     * 1、注册新的AM、来自任意正在结束的AM的终止、取消注册请求、认证来自不同的AM的所有请求
     * 2、确保合法的AM发送的请求传递给RM中的应用程序对象,获取来自所有运行AM的Container的分配和释放请求
     * 3、ApplicationMaster Service确保了任意时间点,任意AM只有一个线程可以发送请求给RM,因为在RM上所有来自
     */
    masterService = createApplicationMasterService();
    createAndRegisterOpportunisticDispatcher(masterService);
    addService(masterService) ;
    rmContext.setApplicationMasterService(masterService);


    // Application权限管理
    applicationACLsManager = new ApplicationACLsManager(conf);

    // 权限管理器
    queueACLsManager = createQueueACLsManager(scheduler, conf);

    /**
     * 维护 applications list,管理app提交,结束,恢复等
     */
    rmAppManager = createRMAppManager();
    rmDispatcher.register(RMAppManagerEventType.class, rmAppManager);

    /**
     * ClientRMService
     * 负责处理面向客户端使用的接口,内部实现了Client和RM之前通信的ApplicationClientProtocol协议
     */
    clientRM = createClientRMService();
    addService(clientRM);
    rmContext.setClientRMService(clientRM);

    /**
     * 负责启动和停止 ApplicationMaster
     */
    applicationMasterLauncher = createAMLauncher();
    rmDispatcher.register(AMLauncherEventType.class, applicationMasterLauncher);
    addService(applicationMasterLauncher);

    // TODO 如果启动YARN联邦,则启动一个FederationstateStoreService服务
    if(HAUtil.isFederationEnabled(conf)) {
      federationStateStoreService = createFederationStateStoreService();
      addIfService(federationStateStoreService);
    }

    // TODO YARN web API服务初始化,默认为false,没启动用,可以通过yarn.webapp.api-service.enable来启用
    if (conf.getBoolean(YarnConfiguration.YARN_API_SERVICES_ENABLE,
        false)) {
      SystemServiceManager systemServiceManager = createServiceManager();
      addIfService(systemServiceManager);
    }

    // 执行这些Service的初始化
      super.serviceInit(conf);
}

7.2.3、ResourceManager的serviceStart()

核心就是 :

ResourceManager.serviceStart() {
    // TODO 刚启动,自动成为Standby
    if (this.rmContext.isHAEnabled()) {
      // TODO 注意参数为 false
      transitionToStandby(false);
    }

    // TODO 开启webapp服务,主要是用户认证服务
    startWepApp();

    // 启动 ResourceManager的所有子服务
    super.serviceStart() {
        // 获取所有服务
        List<Service> services = getServices();

        // ResourceManager的所有子服务,挨着启动
        for(Service service : services) {
            service.start();
        }
    }

    /**
     * TODO 切换成 Active,争抢实现
     * 如果不是HA模式,则自己直接成为active,不用使用StandByTransitionRunnable来进行了
     */
    if (!this.rmContext.isHAEnabled()) {
      transitionToActive();
    }
}

7.3、YARN HA方案 : EmbeddedElector + StandByTransitionRunnable

执行原理 : 首先由EmbeddedElector来做最多3次的尝试选举,如果没有选举成功,则交给StandByTransitionRunnable线程来维护选举

首先来研究YARN HA的实现方案 :
YARN HA实现方案.png

研究源码,核心入口下面的这句话,它的内部负责创建一个选举服务 : ActiveStandbyElectorBasedElectorService

EmbeddedElector elector = createEmbeddedElector();

然后在ResourceManager的serviceInit()和serviceStart()执行的时候,启动 :

ActiveStandbyElectorBasedElectorService.serviceInit() {
    
    // 获取各种基础数据,为选举做数据准备
    String zkQuorum = conf.get(YarnConfiguration.RM_ZK_ADDRESS);
    String rmId = HAUtil.getRMHAId(conf);
    String clusterId = YarnConfiguration.getClusterId(conf);
    localActiveNodeInfo = createActiveNodeInfo(clusterId, rmId);
    String zkBasePath = conf.get(YarnConfiguration.AUTO_FAILOVER_ZK_BASE_PATH, YarnConfiguration.DEFAULT_AUTO_FAILOVER_ZK_BASE_PATH);
    String electionZNode = zkBasePath + "/" + clusterId;
    zkSessionTimeout = conf.getLong(YarnConfiguration.RM_ZK_TIMEOUT_MS, YarnConfiguration.DEFAULT_RM_ZK_TIMEOUT_MS);
    // TODO 默认重试次数,默认值是3
    int maxRetryNum =
        conf.getInt(YarnConfiguration.RM_HA_FC_ELECTOR_ZK_RETRIES_KEY, conf
          .getInt(CommonConfigurationKeys.HA_FC_ELECTOR_ZK_OP_RETRIES_KEY,
            CommonConfigurationKeys.HA_FC_ELECTOR_ZK_OP_RETRIES_DEFAULT));

    // 创建 ActiveStandbyElector
    elector = new ActiveStandbyElector(zkQuorum, (int) zkSessionTimeout, electionZNode, zkAcls, zkAuths, this, maxRetryNum, false) {
        // appClient = ActiveStandbyElectorBasedElectorService
        appClient = app;
        // znodeWorkingDir = /yarn-leader-election/clusterID
        znodeWorkingDir = parentZnodeName;
        // zkLockFilePath = /yarn-leader-election/clusterID/ActiveStandbyElectorLock
        zkLockFilePath = znodeWorkingDir + "/" + LOCK_FILENAME;
        // zkBreadCrumbPath = /yarn-leader-election/clusterID/ActiveBreadCrumb
        zkBreadCrumbPath = znodeWorkingDir + "/" + BREADCRUMB_FILENAME;
        // 重试次数,默认3次
        this.maxRetryNum = maxRetryNum;
    }

    // TODO 确保父节点 zkBasePath 存在
    elector.ensureParentZNode() {
        // TODO 获取ZK链接
        if (zkClient == null) {
          createConnection() {
              // 内部获取一个Zookeeper链接
              zkClient = connectToZooKeeper() {
                  watcher = new WatcherWithClientRef();
                  ZooKeeper zk = createZooKeeper() {
                      return new ZooKeeper(zkHostPort, zkSessionTimeout, watcher);
                  }
                  watcher.waitForZKConnectionEvent(zkSessionTimeout);
                  
              }
          }
        }

        // 级联创建 : /yarn-leader-election/clusterID znode节点
        String pathParts[] = znodeWorkingDir.split("/");
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i < pathParts.length; i++) {
          sb.append("/").append(pathParts[i]);
          String prefixPath = sb.toString();
          createWithRetries(prefixPath, new byte[]{}, zkAcl, CreateMode.PERSISTENT);
        }
    }

    // TODO 进行校验,确保信息正确
    if (!isParentZnodeSafe(clusterId)) {
      notifyFatalError(String.format("invalid data in znode, %s, " +
          "which may require the state store to be reformatted",
          electionZNode));
    }
}

ActiveStandbyElectorBasedElectorService.serviceStart() :

ActiveStandbyElectorBasedElectorService.serviceStart() {
    // TODO 进行选举
    elector.joinElection(localActiveNodeInfo) {
        // 准备znode节点数据(clusterID和rmID)
        appData = new byte[data.length];
        System.arraycopy(data, 0, appData, 0, data.length);

        // 选举
        joinElectionInternal() {
            createLockNodeAsync() {
                zkClient.create(zkLockFilePath, appData, zkAcl, CreateMode.EPHEMERAL, this, zkClient);
            }
        }
    }

}

当上述的 ActiveStandbyElector 尝试创建锁节点的时候,如果成功,则会毁掉processResult()方法

ActiveStandbyElector.processResult(int rc, String path, Object ctx, String name) {
    
    // 获取返回状态码
    Code code = Code.get(rc);
    if (isSuccess(code)) {
      // TODO 进入Active状态
      if (becomeActive()) {
        monitorActiveStatus();
      } else { 
          // TODO 如果切换状态并没有成功,则休息1s之后,重新加入选举
        reJoinElectionAfterFailureToBecomeActive();
      }
    }

    /**
     * 如果锁节点已经存在,但是自己又没有收到OK的状态码提示,证明锁节点是别人创建的,则别人成为active,自己成为standby
     */
    if (isNodeExists(code)) {
      // 切换成为standby
      becomeStandby();

      // TODO 这里会监听Active的状态
      monitorActiveStatus() {
          monitorLockNodeAsync() {
              // TODO 这里其实就是监听判断节点的存在
            zkClient.exists(zkLockFilePath, watcher, this, zkClient);
          }
      }
    }

    // 如果代码走到这里,意味着,创建锁节点不成功,并且锁节点也不存在
    if (shouldRetry(code)) {
      // TODO 如果重试次数小于三次,就在当前循环进行重试
      if (createRetryCount < maxRetryNum) {
        ++createRetryCount;
        // TODO 还是异步创建
        createLockNodeAsync();
        return;
     }

    // TODO 如果重试次数大于了3次,就走这里。通过StandByTransitionRunnable线程来做选举
    fatalError(errorMessage);
}

来看becomeActive()方法的实现 :

ActiveStandbyElector.becomeActive() {
    
    // TODO 如果已经是Active,直接返回就好
    if (state == State.ACTIVE) {
      return true;
    }

    // TODO 对失效的 active 节点的节点信息做一些校验和处理,如果是YARN什么也不做,如果是HDFS这里是需要找
    // TODO hdfs-site.xml 中 dfs.ha.fencing.methods 的配置的
    Stat oldBreadcrumbStat = fenceOldActive();

    // TODO 写一个 Crumb znode
    writeBreadCrumbNode(oldBreadcrumbStat);

    // TODO 让RM成为Active
    appClient.becomeActive() {
        // 取消定时器
        cancelDisconnectTimer();

        // RMAdminService 刷新信息
          rm.getRMContext().getRMAdminService().transitionToActive(req) {
              // TODO 如果RM本身就是Active,直接返回
            if (isRMActive()) {
              return;
            }

            // TODO 重新获取用户的权限控制
              refreshAdminAcls(false);

              // TODO 重新获取节点、队列、用户等信息
              refreshAll();

              // TODO 重点,ResourceManager切换为Active。启动RMActiveServices中重要的子服务
              rm.transitionToActive() {
                  // TODO 启动活动的服务
                  startActiveServices() {
                      if (activeServices != null) {
                      clusterTimeStamp = System.currentTimeMillis();
                      // TODO 启动Active Service
                      activeServices.start();
                    }
                  }

                  rmContext.setHAServiceState(HAServiceProtocol.HAServiceState.ACTIVE);
              }
          }
    }

    // TODO 把选举实例状态更改为active
    state = State.ACTIVE;
    return true;
}

如果ResourceManager在经历过最大3次(该参数可以通过ha.failover-controller.active-standby-elector.zk.op.retries去配置),尝试创建锁节点没有成功的话,就会转而使用StandByTransitionRunnable
线程去执行选举

ActiveStandbyElector.fatalError(errorMessage) {
    // 更新状态为INIT,并且关闭zk连接
    reset() {
        // 更新状态为INIT,并且关闭ZK链接
        state = State.INIT;
        // 关闭ZK链接
        terminateConnection();
    }

    // TODO 这里是核心;StandByTransitionRunnable
    appClient.notifyFatalError(errorMessage) {
        // 在创建 AsyncDispatcher的时候,注册了一个事件 : RMFatalEvent => RMFatalEventDispatcher
        // 代码转到 RMFatalEventDispatcher 的 handle()
        rm.getRMContext().getDispatcher().getEventHandler().handle(new RMFatalEvent(RMFatalEventType.EMBEDDED_ELECTOR_FAILED, errorMessage));
    }
}

StandByTransitionRunnable是在初始化RMActiveServices的时候进行初始化的。在这之前,创建AsyncDispatcher的时候,其实已经注册了一个Event和EventHandler的映射 :

dispatcher.register(RMFatalEventType.class, new ResourceManager.RMFatalEventDispatcher());

当提交一个 RMFatalEvent的时候,则RMFatalEventDispatcher的handle方法就会被执行,从而触发 :

handleTransitionToStandByInNewThread();

方法的执行,然后启动了StandByTransitionRunnable线程

handleTransitionToStandByInNewThread() {
    
    // TODO 创建一个线程
    Thread standByTransitionThread = new Thread(activeServices.standByTransitionRunnable);
    // TODO 启动线程
    standByTransitionThread.start() {

        // 转到 StandByTransitionRunnable.run()
        StandByTransitionRunnable.run() {
            // TODO 先让自己成为standby,然后进行选举
              transitionToStandby(true);

              // TODO 获取选举服务
              EmbeddedElector elector = rmContext.getLeaderElectorService();

              // TODO 重新进行选举
            elector.rejoinElection() {
                // TODO 先退出选举
                elector.quitElection(false);
                // TODO 参与选举
                elector.joinElection(localActiveNodeInfo);
            }
        }

    }
}

所以说,RM首先通过ActiveStandbyElector来执行选举,如果尝试最大3次都没有创建锁节点成功,则会使用StandByTransitionRunnable线程来进行选举

7.4、YARN 集群启动NodeManger启动源码详解

再来了解YARN的NodeManager内部的组成。可以针对每个组件各个击破
NodeManager内部组件.png

NodeManager启动核心 :

NodeManager.main() {
    // NodeManager初始化
    NodeManager nodeManager = new NodeManager();

    // 启动NodeManager
    nodeManager.initAndStartNodeManager(conf, false) {
        // 服务初始化 ===> 调用NodeManager.serviceInit()
        this.init(conf);

        // 服务启动 ==> 调用 NodeManager.serviceStart()
        this.start();
    }
}

NodeManager是Service的子类。查看继承结构 :

public class NodeManager extends CompositeService implements EventHandler<NodeManagerEvent>, NodeManagerMXBean {}

public class CompositeService extends AbstractService {}

public interface Service extends Closeable {}

image.png
注意Service这个接口,在YARN中有非常多类似的这种Service实现,其中,ResourceManager就是由很多Service实现组成的

7.4.1、NodeManager的serviceInit()

NodeManager.serviceInit(Configuration conf) {
    
    // TODO NM 状态持久化服务
    /**
     * 如果启用了 yarn.nodemanager.recovery.enabled = true,默认是false
     * yarn.nodemanager.recovery.enabled = true,则实现是 : NMLeveldbStateStoreService
     * yarn.nodemanager.recovery.enabled = false,则实现是 : NMNullStateStoreService 所有方法都是空实现
     */
    initAndStartRecoveryStore(conf);

    // TODO SecretManager,token的管理
    NMContainerTokenSecretManager containerTokenSecretManager =
        new NMContainerTokenSecretManager(conf, nmStore);
    NMTokenSecretManagerInNM nmTokenSecretManager =
        new NMTokenSecretManagerInNM(nmStore);
    recoverTokens(nmTokenSecretManager, containerTokenSecretManager);

    // TODO ApplicationACLsManager,权限管理器
    this.aclsManager = new ApplicationACLsManager(conf);

    // TODO NodeManager 节点的本地文件夹健康检查服务
    this.dirsHandler = new LocalDirsHandlerService(metrics);

    // TODO 默认是false; yarn.nodemanager.distributed-scheduling.enabled
    boolean isDistSchedulingEnabled =
        conf.getBoolean(YarnConfiguration.DIST_SCHEDULING_ENABLED,
            YarnConfiguration.DEFAULT_DIST_SCHEDULING_ENABLED);


    // TODO 上下文对象
    this.context = createNMContext(containerTokenSecretManager,
        nmTokenSecretManager, nmStore, isDistSchedulingEnabled, conf);

    // TODO ResourcePluginManager 资源插件服务
    ResourcePluginManager pluginManager = createResourcePluginManager();
    pluginManager.initialize(context);
    ((NMContext)context).setResourcePluginManager(pluginManager);


    // TODO yarn.nodemanager.container-executor.class 执行服务; DefaultContainerExecutor
    // TODO org.apache.hadoop.yarn.server.nodemanager.DefaultContainerExecutor; 其实默认就是DefaultContainerExecutor
    // TODO 底层是Linux和windows两种
    ContainerExecutor exec = createContainerExecutor(conf);
    exec.init(context);

    // TODO 文件清理服务
    DeletionService del = createDeletionService(exec);
    addService(del);

    // TODO 创建 AsyncDispatcher对象
    this.dispatcher = createNMDispatcher();


    // TODO 节点健康检查服务
    this.nodeHealthChecker = new NodeHealthCheckerService(dirsHandler);
    addService(nodeHealthChecker);

    /**
     * TODO 节点状态变更服务
     * NodeStatusUpdaterImpl
     * 1、负责 : NodeManager 向 ResourceManager 注册
     * 2、负责 : 心跳
     */
    nodeStatusUpdater = createNodeStatusUpdater(context, dispatcher, nodeHealthChecker);


    /**
     * TODO ConfigurationNodeLabelsProvider 或者 ScriptBasedNodeLabelsProvider
     */
    nodeLabelsProvider = createNodeLabelsProvider(conf);
    if (nodeLabelsProvider != null) {
      addIfService(nodeLabelsProvider);
      nodeStatusUpdater.setNodeLabelsProvider(nodeLabelsProvider);
    }

    /**
     * TODO ConfigurationNodeAttributesProvider或者ScriptBasedNodeAttributesProvider
     */
    nodeAttributesProvider = createNodeAttributesProvider(conf);
    if (nodeAttributesProvider != null) {
      addIfService(nodeAttributesProvider);
      nodeStatusUpdater.setNodeAttributesProvider(nodeAttributesProvider);
    }


    // TODO 节点资源监控服务
    nodeResourceMonitor = createNodeResourceMonitor();
    addService(nodeResourceMonitor);
    ((NMContext) context).setNodeResourceMonitor(nodeResourceMonitor);

    // TODO Container 容器管理服务 ContainerManagerImpl
    containerManager =
        createContainerManager(context, exec, del, nodeStatusUpdater,
        this.aclsManager, dirsHandler);
    addService(containerManager);
    ((NMContext) context).setContainerManager(containerManager);

    // TODO NM节点的日志聚合服务 NMLogAggregationStatusTracker
    this.nmLogAggregationStatusTracker = createNMLogAggregationStatusTracker(
        context);
    addService(nmLogAggregationStatusTracker);
    ((NMContext)context).setNMLogAggregationStatusTracker(
        this.nmLogAggregationStatusTracker);

    // TODO web服务
    WebServer webServer = createWebServer(context, containerManager
        .getContainersMonitor(), this.aclsManager, dirsHandler);
    addService(webServer);
    ((NMContext) context).setWebServer(webServer);
    int maxAllocationsPerAMHeartbeat = conf.getInt(
        YarnConfiguration.OPP_CONTAINER_MAX_ALLOCATIONS_PER_AM_HEARTBEAT,
        YarnConfiguration.
            DEFAULT_OPP_CONTAINER_MAX_ALLOCATIONS_PER_AM_HEARTBEAT);
    // TODO Container申请分配器 OpportunisticContainerAllocator
    ((NMContext) context).setQueueableContainerAllocator(
        new DistributedOpportunisticContainerAllocator(
            context.getContainerTokenSecretManager(),
            maxAllocationsPerAMHeartbeat));


    // TODO 给中央异步事件调度器注册事件和事件处理器
    dispatcher.register(ContainerManagerEventType.class, containerManager);
    dispatcher.register(NodeManagerEventType.class, this);
    addService(dispatcher);

    // TODO JVM监听监视器服务
    pauseMonitor = new JvmPauseMonitor();
    addService(pauseMonitor);
    metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);

    DefaultMetricsSystem.initialize("NodeManager");

    // TODO timelineService,timeline服务
    if (YarnConfiguration.timelineServiceV2Enabled(conf)) {
      this.nmCollectorService = createNMCollectorService(context);
      addService(nmCollectorService);
    }

    // TODO 所有service进行初始化
    super.serviceInit(conf);
}

7.4.2、NodeManager的serviceStart()

NodeManager.serviceStart() {
    // 因为NodeManager中并没有重写serviceStart()方法,所以调用父类的serviceStart()方法
    AbstractService.serviceStart() {
        CompositeService.serivceStart() {
            // 获取所有子服务
            List<Service> services = getServices();
            // 遍历每个子服务,服务启动
            for(Service service : services) {
                service.start();
            }
        }
    }
}

7.5、YARN AsyncDispatcher事件驱动机制详解

YARN采用了基于事件驱动的并发模型,该模型能够大大增强并发性,从而提供系统整体性能。为了构建该模型,YARN将各种处理逻辑抽象成事件和对应事件调度器,并将每种事件的处理过程分割成多个步骤,用有限状态机标识。AsyncDispatcher是YARN的中央异步调度器。在ResourceManager中,几乎所有的事件都通过AsyncDispatcher进行事件的派发
中央异步处理器模型.png

整个处理过程大致为 : 处理请求会作为事件进入系统,由中央异步调度器(AsyncDispatcher)负责传递给相应事件调度器(EventHandler),该事件调度器可能将该事件转发给另一个事件调度器,也可能交给一个带有有限状态机的事件处理器,其处理结果也以事件形式输出给中央处理器。而新的事件会再次被中央异步调度器转发给下一个事件调度器,直至处理完成(到达终止条件)

在YARN中,所有核心服务实际上都是一个中央异步调度器,包括ResourceManager、NodeManager、MRAppMaster等,他们维护了事先注册的事件与事件处理器,并根据接收的事件类型驱动服务的执行,以MRAppMaster为例,它内部包含一个中央异步调度器AsyncDispatcher,并注册了TaskAttemptEvent/TaskAttemptImpl、TaskEvent/TaskEventImpl、JobEvent/JobImpl等一系列事件/事件处理器,由中央异步调度器统一管理和调度

先看AsyncDispatcher内部成员 :

public class AsyncDispatcher extends AbstractService implements Dispatcher {
    
    // TODO Event 队列
    private final BlockingQueue<Event> eventQueue;

    // TODO 通用事件处理器
      private final EventHandler<Event> handlerInstance = new GenericEventHandler();

      // TODO 处理线程
      private Thread eventHandlingThread;

      // TODO Event 和 Handler的映射关系
    protected final Map<Class<? extends Enum>, EventHandler> eventDispatchers;

    // TODO 创建线程的一个方法
      Runnable createThread() {
          return new Runnable() {
              while (!stopped && !Thread.currentThread().isInterrupted()) {
                  // 从队列中取出事件
                  event = eventQueue.take();
                  // 进行分发
                  dispatch(event);
              }
          }
      }

      @Override
    protected void serviceInit(Configuration conf) throws Exception{
      super.serviceInit(conf);
      this.detailsInterval = getConfig().getInt(YarnConfiguration.
                    YARN_DISPATCHER_PRINT_EVENTS_INFO_THRESHOLD,
            YarnConfiguration.
                    DEFAULT_YARN_DISPATCHER_PRINT_EVENTS_INFO_THRESHOLD);
    }

    @Override
    protected void serviceStart() throws Exception {
      //start all the components
      super.serviceStart();
      // TODO 调用创建消费eventQueue队列中事件的线程
      eventHandlingThread = new Thread(createThread());
      eventHandlingThread.setName(dispatcherThreadName);
      eventHandlingThread.start();
    }

    protected void dispatch(Event event) {

    // TODO 根据事件找到事件对应的类型
    Class<? extends Enum> type = event.getType().getDeclaringClass();

    // TODO 获取对应的 EventHandler
    EventHandler handler = eventDispatchers.get(type);
    
    // TODO EventHandler执行事件的处理器
    handler.handle(event);
   }


  @Override
  public void register(Class<? extends Enum> eventType,
      EventHandler handler) {
    EventHandler<Event> registeredHandler = (EventHandler<Event>)
    // TODO 事件 和 事件处理器的注册
    eventDispatchers.put(eventType, handler);
  }

  @Override
  public EventHandler<Event> getEventHandler() {
    return handlerInstance;
  }

  class GenericEventHandler implements EventHandler<Event> {
      public void handle(Event event) {
          // TODO 也是放入到队列中
        eventQueue.put(event);
      }
  }
}

所以调用的时候先register(event,EventHandler),再执行 getEventHandler().put(event);这样就可以让eventHandlingThread线程take event,然后dispatch执行了

在ResourceManager或者NodeManager等组件中,都有一个AsyncDispatcher中央异步事件调度器,在初始化的时候,会创建EventHandler和对应的事件类型,注册到AsyncDispatcher中来,如果AsyncDispatcher接收到一个事件,由GenericEventHandler完成eventQueue的容量维护,并且将事件加入到eventQueue中,负责消费eventQueue的eventHandlerThread则从eventDispatchers获取注册的事件对应的EventHandler来完成处理。所以,这样子来看,AsyncDispatcher就是一个中央调度分发器

7.6、YARN StateMachine 有限状态机

状态机由一组状态组成,这些状态分为三类 : 初始状态、中间状态和最终状态。状态机从初始状态开始运行,经过一系列中间状态后,到达最终状态并退出。在一个状态机中,每个状态都可以接收到一组特定事件,并根据具体的事件类型转换到另一个状态。当状态机转换到最终状态时,则退出

在YARN中,每种状态转换(doTransition方法区执行状态转换,addTransition注册状态转换)由一个四元组标识,分别是转换前状态(preState)、转换后状态(postState)、事件(event)和回调函数(hook=Transition)

YARN 定义了三种状态转换方式,具体如下 :

  • 一个初始状态、一个最终状态、一种事件 : 经过处理之后,无论如何,进入到一个唯一状态
  • 一个初始状态、多个最终状态、一种事件 : 不同的逻辑处理结果,可能导致进入不同的状态
  • 一个初始状态、一个最终状态、多种事件 : 多个不同的事件,可能触发多个不同状态的转换
    YARN StateMachine有限状态机.png

YARN自己实现了一个非常简单的状态机库,在org.apache.hadoop.yarn.state包中,YARN对外提供了一个状态机工厂StatemachineFactory,它提供多种addTransition方法供用户添加各种状态转换,一旦状态添加完毕后,可通过调用installTopology完成一个状态机的构建

public interface StateMachine<STATE extends Enum<STATE>, EVENTTYPE extends Enum<EVENTTYPE>, EVENT> {
    // TODO 获取状态机,当前状态
      public STATE getCurrentState();

      // TODO 进行状态转移
      public STATE doTransition(EVENTTYPE eventType, EVENT event) throws InvalidStateTransitionException;
}

来看StatemachineFactory的定义 :

final public class StateMachineFactory<OPERAND, STATE extends Enum<STATE>, EVENTTYPE extends Enum<EVENTTYPE>, EVENT> {

    // TODO 就是将状态机的一个个过度的 ApplicableTransition 实现串联为一个列表,每个节点包含一个 ApplicableTransition 实现及指向下一个节点的引用
    // 链表 每次注册时候,只是往这个 transitionListNode 链表中,增加下一个 node 节点,之后会有一个 installTopology() 方法调用来生成状态机表
      private final TransitionsListNode transitionsListNode;

      // TODO 状态拓扑列表,为了提高检索状态对应的过度 map 而冗余的数据结构
    /* key = STATE
      * value = Map<EVENTTYPE, Transition<OPERAND, STATE, EVENTTYPE, EVENT>>
      *      key =  EVENTTYPE
      *      value = Transition<OPERAND, STATE, EVENTTYPE, EVENT>
      *
      * 状态1 event1 transition1
      * 状态2 event2 transition2
      * 状态1 event3 transition3
      * 状态2 event4 transition4
      *
      * OPERAND 状态机
      * STATE 切换后的状态
      * EVENTTYPE 事件类型
      * EVENT 事件
     */
      private Map<STATE, Map<EVENTTYPE, Transition<OPERAND, STATE, EVENTTYPE, EVENT>>> stateMachineTable;

      // TODO 对象创建时,内部有限状态机的默认初始状态
    // TODO 比如 JobImpl 的内部状态机默认初始状态就是 JobStateInternal.NEW
    private STATE defaultInitialState;


    // 注册状态转移四元组
    public StateMachineFactory<OPERAND, STATE, EVENTTYPE, EVENT> addTransition(STATE preState, STATE postState, EVENTTYPE eventType) {
        return addTransition(preState, postState, eventType, null);
      }

      // 构建一个StatemachineFactory
      public StateMachineFactory<OPERAND, STATE, EVENTTYPE, EVENT> installTopology() {
        // TODO 实例化 makeStateMachineTable,生成状态机表
        return new StateMachineFactory<OPERAND, STATE, EVENTTYPE, EVENT>(this, true);
      }


    // 一个负责执行某个preState到postState状态转移的代码执行单元
      private STATE doTransition(OPERAND operand, STATE oldState, EVENTTYPE eventType, EVENT event)
      throws InvalidStateTransitionException {
    // TODO 获取到旧状态对应的一个map结构 : 事件和Transition
    Map<EVENTTYPE, Transition<OPERAND, STATE, EVENTTYPE, EVENT>> transitionMap= stateMachineTable.get(oldState);
    if (transitionMap != null) {
      // TODO 根据事件获取到对应的Transition
      Transition<OPERAND, STATE, EVENTTYPE, EVENT> transition
          = transitionMap.get(eventType);
      if (transition != null) {
        // TODO 调用Transition执行状态转移
        return transition.doTransition(operand, oldState, event, eventType);
      }
    }
    throw new InvalidStateTransitionException(oldState, eventType);
  }

  private class SingleInternalArc implements Transition<OPERAND, STATE, EVENTTYPE, EVENT> {}
  private class MultipleInternalArc implements Transition<OPERAND, STATE, EVENTTYPE, EVENT>{}

}

YARN中实现了多个状态机对象,包括 :

  • ResourceManager 中的RMAppImpl、RMAppAttemptImpl、RMContainerImpl和RMNodeImpl等
  • NodeManager中的ApplicationImpl、ContainerImpl和LocalizedResource等
  • MRAppMaster中的JobImpl、TaskImpl和TaskAttemptImpl等

8、YARN ResourceManger 核心设计

8.1、ReosurceManager基本职能

ResourceManager基本职能.png

  • ResourceTracker : NodeManager通过该RPC协议向ResourceManager注册、汇报节点健康状况和Container运行状态,并领取ResourceManager下达的命令,这些
    命令包括重新初始化、清理Container等,在该RPC协议中,ReosurceManager扮演RPC Server的角色(由内部组件ResourceTrackerService实现),而NodeManager扮演
    RPC Client的角色,NodeManager总是周期性的主动向ResourceManager发送请求,并通过领取下达自己的命令
  • ApplicationMasterProtocol : 应用程序的ApplicationMaster通过该RPC协议向ResourceManager注册、申请资源和释放资源。在该协议中,ApplicationMaster
    扮演RPC Client的角色,而ResourceManager扮演RPC Server的角色
  • ApplicationClientProtocol : 应用程序的客户端通过该RPC协议向ResourceManager提交应用程序、查询应用程序状态和控制应用程序(比如杀死应用程序)等。在该协议
    中,应用程序客户端扮演PRC Client的角色,而ResourceManager扮演RPC Server的角色

    8.2、ResourceManager内部组成

    ResourceManager内部组成.png

    8.3、事件和事件处理器

    ResourceManager事件与事件处理器.png
    从事件驱动角度展示了ResourceManager内部各类事件与事件处理器的相互关系 :
    ResourceManager内部各类事件与事件处理器的相互关系.png

9、YARN NodeManager 核心设计

NodeManager(NM)是YARN中单个节点上的代理,它管理Hadoop集群中单个计算节点,功能包括与ResourceManager保持通信、管理Container的生命周期、监控每个Container的资源使用(内存、CPU等)情况,追踪节点健康状况、管理日志和不同应用程序的附属服务(auxliary service)

9.1、NodeManager基本职责

NodeManager基本职能.png

  • ResourceTrackerProtocol : NodeManager通过该RPC协议向ResourceManager注册、汇报节点健康状况和Container运行状态,并
    领取ResourceManager下达的命令,包括重新初始化、清理Container占用资源等。在该RPC协议中,ResourceManager扮演RPC Server的角色,而NodeManager扮演RPC Client的角色(由内部组件NodeStatusUpader实现),NodeManager与ResourceManager之间采用了"pull模型",NodeManager总是周期性的主动向ResourceManager发送请求,并领取下达自己的命令
  • ContainerManagementProtocol : 应用程序的ApplicationMaster通过该PRC协议向NodeManager发起针对Container的相关操作,
    包括启动Contaner、杀死Container,获取Container执行状态等。在该协议中,ApplicationMaster扮演PRC Client的角色,而NodeManager扮演PRC Server的角色(由内部组件ContainerManager实现),换句话说,NodeManager与ApplicationMaster之间采用了"push模型",ApplicationMaster可以将Container相关操作第一时间告诉NodeManager,相比"pull模型",可大大降低时间延迟

    9.2、NodeManager内部组成

    NodeManager内部组成.png

    9.3、事件和事件处理器

    NodeManager事件和事件处理器.png

NodeManager中央异步事件处理器
NodeManager中央异步事件处理器.png

10、MapReduce ON YARN 整体流程

MapReduce ON YARN整体流程.png

11、MapReduce Job提交

从 Hadoop源码的自带示例程序,编写两个提交MR Job的命令示例 :

hadoop jar /home/hadoop-3.3.1/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar pi 5 5
hadoop jar /home/hadoop-3.3.1/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar wordcount /journey/wordcount.txt /journey/output

hadoop命令在hadoop 3.x源码项目中的位置是 :

hadoop-main
    hadoop-common-project
        hadoop-common
            src
                main
                    bin
                        hadoop

$HADOOP_HOME/bin/hadoop 是 Hadoop 的主入口脚本,解析命令行参数后,最终会调用 Java 命令来启动 JVM 并运行指定的 JAR 文件

关键步骤:

1、脚本根据 jar 子命令调用 org.apache.hadoop.util.RunJar
2、将 /home/hadoop-3.3.1/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar 和其他参数传递给 JVM

RunJar 类
入口类:org.apache.hadoop.util.RunJar

此类的主要职责是:
1、解析 JAR 文件:

使用 java.util.jar.JarFile 读取 JAR 的 META-INF/MANIFEST.MF 文件,找到主类 (Main-Class)。
如果未指定 Main-Class,需要手动指定要运行的主类

2、加载主类:

利用反射机制加载指定的主类。

3、调用主类的 main 方法:

将剩余参数传递给主类的 main 方法执行。

对于 hadoop-mapreduce-examples-3.3.1.jar,Main-Class 是:

org.apache.hadoop.examples.ExampleDriver

11.1、RunJar DEBUG验证

1、在 hadoop-env.sh 中为所有 JVM 进程添加调试参数:

export HADOOP_OPTS="$HADOOP_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5008"

2、mr程序提交

hadoop jar /opt/mapreduce-demo-1.0-SNAPSHOT.jar com.journey.mr.wc.WCJob /journey/wordcount.txt /journey/output

3、IDEA deubg
image.png

image.png
可以看到直接反射调用我们自己编写应用程序的main方法


journey
32 声望21 粉丝