1、YARN的产生背景和架构剖析
1.1、Hadoop MRv1不足
原 MapReduce 框架也称MRv1,它是一个主从式架构。主节点JobTracker负责集群的资源管理和处理Client请求,从节点TaskTracker负责管理资源和执行任务。不仅仅存在JobTracker的SPOF问题,而且JobTracker的负载非常高,集群的资源管理也非常粗暴不合理
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平台的通用性,逐渐演变成一个大数据基础平台,甚至可以理解成用来解决大数据问题的分布式操作系统
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
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
- 负责资源的分配与调度
内部组成结构如下 :
所有的这些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的执行
步骤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的命令
所有的这些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 实例案例
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文件
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的
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是一个接口,他的抽象子类 : 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 :
在NodeManager中也包含了很多的Service,NodeManager自己本身一是一个Service :
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 {}
如果要研究一个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的实现方案 :
研究源码,核心入口下面的这句话,它的内部负责创建一个选举服务 : 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启动核心 :
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 {}
注意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进行事件的派发
整个处理过程大致为 : 处理请求会作为事件进入系统,由中央异步调度器(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自己实现了一个非常简单的状态机库,在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基本职能
- 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内部组成
8.3、事件和事件处理器
从事件驱动角度展示了ResourceManager内部各类事件与事件处理器的相互关系 :
9、YARN NodeManager 核心设计
NodeManager(NM)是YARN中单个节点上的代理,它管理Hadoop集群中单个计算节点,功能包括与ResourceManager保持通信、管理Container的生命周期、监控每个Container的资源使用(内存、CPU等)情况,追踪节点健康状况、管理日志和不同应用程序的附属服务(auxliary service)
9.1、NodeManager基本职责
- 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内部组成
9.3、事件和事件处理器
NodeManager中央异步事件处理器
10、MapReduce ON YARN 整体流程
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
可以看到直接反射调用我们自己编写应用程序的main方法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。