YARN-RPC网络通信架构设计

1,079 阅读8分钟

YARN 高并发网络通信 RPC 架构设计和源码实现

一、 YARN 架构演进

1.Hadoop-2.x 以前 MapReduce 的架构设计和实现:

主节点:JobTracker 做全局资源管理和调度

从节点:TaskTracker 单个主机的资源管理和调度

image-20210922201401998.png

架构问题:

  • 单点故障,可靠性低

    只有一个JobTracker,只要这一台主机死机,整个MapReduce集群将瘫痪

  • 单点瓶颈,扩展性差

    每个节点的applicationMaster都在JobTracker上面,当从节点数量较多的时候JobTracker的工作压力将会非常的大,集群的稳定性将会非常差

  • 资源管理和任务执行强耦合

    资源管理和任务调度都是使用mapreduce,意味着该集群只能运行使用mapreduce的Api编写的代码、使用mapreduce的Api编写的代码也不能运行在其他的地方

  • 资源利用率低

    使用Slot进行资源管理,但是Slot是分为MapSlot和ReduceSlot,并且这两者是不可以相互使用对方资源,这样可能会出现,有空闲的Slot出现,(Map和Reduce各有四个,但是现在Map任务有6个,reduce任务有2个,导致想要资源的任务得不到资源,不用资源的不放资源,导致任务无法完成)

  • 不支持多种分布式计算框架

    只支持MapReduce程序的运行,不支持其他计算框架

2.Hadoop-2.x 版本之后,Hadoop 的架构发生了变化:

image-20210922203020021.png

image-20210922203130376.png

Hadoop YARN 架构优势:

  • 极大减小了 JobTracker 的资源消耗

    新的架构,每个节点的ApplicationMaster在各自的从节点上面,大大节省了JobTracker 的资源消耗

  • YARN 中的 ApplicationMaster 只是一个规范

    ApplicationMaster是一个规范,意味着只要满足了这个规范的要求,任何计算框架都可以使用yarn进行资源管理和调度

  • YARN 中的 Container 的资源抽象比 Slot 更合理

    Container 是不分类的,也是一个规范,可以在里面运行各自满足规范的应用程序

  • 整合 ZooKeeper 解决 RM 的 SPOF 问题

    结合Zookeeper实现了HA高可用,避免了出现单点故障问题

二、高并发 YARN RPC 架构设计

1.Hadoop RPC 概念

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

简单说就是客户端将想要使用的方法和参数告诉服务器端,服务器运行该方法,最后将结果返回给客户端。

image-20210922204051011.png

图示为一个简单的例子,RPC服务端为中国,服务端的各种组件代表中国的各个部门,其他国家为客户端,当其他国家要发送消息给中国的具体部门的时候,只需要将消息告知中国大使,由大使代为传达,而中国大使和中国具体部门的具体交流方式外国方面是不需要知道的,是透明的。

Hadoop 作为一款成熟稳定,功能复杂强大的分布式系统,底层必然涉及到大量组件之间的相互通信。一款高性能,可扩展,可维护的 RPC 网络通信框 架,必然是 Hadoop 大厦之根基。所以请看,现行 Hadoop 项目的结构:

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

2.Hadoop RPC 架构实现

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

image-20210922205256788.png

YARN RPC 服务端的工作大致可以分为四个阶段:

  • 第一个阶段:Server 初始化和启动

  • 第二个阶段:接收请求,封装 RpcCall

  • 第三个阶段:处理 RpcCall 请求

  • 第四个阶段:返回结果 其实,这个地方讲的就是一个 RpcServer,RpcServer 并不是指一台服务器,事实上,一台硬件服务器之上是可以运行多个 RpcServer 的。

  • NameNode(多个 RpcServer)

    • 第一个 RpcServer:给 client 提供服务
    • 第二个 RpcServer:给 DataNode 提供服务
  • ResourceManager(多个 RpcServer)

3.高并发 YARN RPC 实战案例

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

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

YARN RPC Writable 案例实现

首先来看代码结构:

image-20210923085031438.png

第一步:首先定义一个 RPC 协议

/*************************************************
 *  注释: RPC协议: 用来定义服务
 *  要实现 VersionedProtocol 这个接口: 不同版本的 Server 和 Client 之前是不能进行通信的
 */
public interface BussinessProtocol {

    void mkdir(String path);
    String getName(String name);

    long versionID = 345043000L;
}

第二步:定义一个 RPC BussinessProtocol 通信协议的服务实现组件:

public class BusinessIMPL implements BussinessProtocol {
    @Override
    public void mkdir(String path) {
        System.out.println("成功创建了文件夹 :" + path);
    }

    @Override
    public String getName(String name) {
        System.out.println("成功打了招呼: hello :" + name);
        return "bigdata";
    }
}

第三步:通过 Hadoop RPC 构建一个 RPC 服务端,通过 BussinessProtocol 协议对外提供服务

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.RPC;

import java.io.IOException;
/*************************************************
 *  注释: 模拟 Hadoop 构建一个 RPC 服务端
 */
public class MyServer {
    public static void main(String[] args) {
        try {
            /*************************************************
             *  注释: 构建一个 RPC server 端
             *  服务端,提供了一个 BussinessProtocol 协议的 BusinessIMPL 服务实现
             */
            RPC.Server server = new RPC.Builder(new Configuration())
                    .setProtocol(BussinessProtocol.class)
                    .setInstance(new BusinessIMPL())
                    .setBindAddress("localhost")
                    .setPort(6789)
                    .build();

            //  注释: RPC Server 启动
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第四步:构建一个 RPC 客户端,用来给发起 RPC 请求给 RPC 服务端

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.RPC;

import java.io.IOException;
import java.net.InetSocketAddress;

/*************************************************
 * TODO_MA 马中华 https://blog.csdn.net/zhongqi2513
 *  注释: 构建 RPC 客户端
 */
public class MyClient {
    public static void main(String[] args) {
        try {
            /*************************************************
             *  注释: 获取了服务端中暴露了的服务协议的一个代理。
             *  客户端通过这个代理可以调用服务端的方法进行逻辑处理
             */
            BussinessProtocol proxy = RPC.getProxy(BussinessProtocol.class, BussinessProtocol.versionID,
                    new InetSocketAddress("localhost", 6789), new Configuration());
            /*************************************************
             *  注释: 在客户端调用了服务端的代码执行,真正的代码执行是在服务端的
             */
            proxy.mkdir("/home/bigdata/apps");
            String rpcResult = proxy.getName("bigdata");
            System.out.println("从 RPC 服务端接收到的 getName RPC 请求的响应结果: " + rpcResult);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第五步:测试

  • 先运行服务端
  • 然后运行客户端,发起 RPC 请求,查看是否得到 RPC 请求的响应结果。 服务端输出:

成功创建了文件夹 :/home/bigdata/apps 成功打了招呼: hello :bigdata

客户端接收到结果:

从 RPC 服务端接收到的 getName RPC 请求的响应结果: bigdata

YARN RPC Protobuf 案例实现

首先来看项目结构:

image-20210923085001903.png

第一步:定义协议 MyResourceTracker

package com.mazh.rpc.protobuf.demo2;

import com.mazh.rpc.protobuf.demo2.proto.MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto;
import com.mazh.rpc.protobuf.demo2.proto.MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto;

/*************************************************
 *  注释: 该协议的参数 和 返回值,都是由 Protobuf 生成的 Java 对象
 */
public interface MyResourceTracker {
    MyRegisterNodeManagerResponseProto registerNodeManager(MyRegisterNodeManagerRequestProto request) throws Exception;
}

第二步:定义 Protobuf 文件:

option java_package = "com.mazh.rpc.protobuf.demo2.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);
}
option java_package="com.mazh.rpc.protobuf.demo2.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;
}

第三步:对 proto 文件,进行编译生成 Java 文件,关于如何安装 protobuf 在此不做介绍。具体的编译命令:

protoc --proto_path=./ --java_out ../../../../../../ ./MyResourceTrackerMessage.proto
protoc --proto_path=./ --java_out ../../../../../../ ./MyResourceTracker.proto

第四步:对 MyResourceTracker 通信协议进行具体的逻辑实现

package com.mazh.rpc.protobuf.demo2;

import com.mazh.rpc.protobuf.demo2.proto.MyResourceTrackerMessage;
/*************************************************
 *  注释: 通信协议的具体实现
 */
public class MyResourceTrackerService implements MyResourceTracker {

    @Override
    public MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto registerNodeManager(
            MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto request) throws Exception {

        // 注释: 构建一个响应对象,用于返回
        MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto.Builder builder = 
MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto
                .newBuilder();

        //  注释: 输出注册的消息
        String hostname = request.getHostname();
        int cpu = request.getCpu();
        int memory = request.getMemory();
        System.out.println("NodeManager 的注册消息: hostname = " + hostname + ", cpu = " + cpu + ", memory = " + memory);

        //  注释: 直接暴力返回 True
        builder.setFlag("true");
        MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto response = builder.build();
        return response;
    }
}

第六步:编写 proto 的协议接口

package com.mazh.rpc.protobuf.demo2;

import com.mazh.rpc.protobuf.demo2.proto.MyResourceTracker;
import org.apache.hadoop.ipc.ProtocolInfo;

/*************************************************
 *  注释: 编写 proto 的协议接口
 */
@ProtocolInfo(protocolName = "com.mazh.rpc.protobuf.demo2.ResourceTrackerPB", protocolVersion = 1)
public interface MyResourceTrackerPB extends MyResourceTracker.MyResourceTrackerService.BlockingInterface {

}

第七步:编写 proto 的协议接口实现

package com.mazh.rpc.protobuf.demo2;

import com.google.protobuf.RpcController;
import com.mazh.rpc.protobuf.demo2.proto.MyResourceTrackerMessage;

/*************************************************
 *  注释: 编写 proto 的协议接口实现
 */
public class MyResourceTrackerServerSidePB implements MyResourceTrackerPB {

    final private MyResourceTracker server;

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

    @Override
    public MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto registerNodeManager(RpcController controller,
            MyResourceTrackerMessage.MyRegisterNodeManagerRequestProto request){

        try {
            return server.registerNodeManager(request);
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

第八步:编写 RPC Server 实现

package com.mazh.rpc.protobuf.demo2.server;
import com.google.protobuf.BlockingService;
import com.mazh.rpc.protobuf.demo2.MyResourceTrackerPB;
import com.mazh.rpc.protobuf.demo2.MyResourceTrackerServerSidePB;
import com.mazh.rpc.protobuf.demo2.MyResourceTrackerService;
import com.mazh.rpc.protobuf.demo2.proto.MyResourceTracker;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ipc.RPC;

import java.io.IOException;

/*************************************************
 *  注释: 基于 Hadoop 的 RPC API 编写的一个 RpcServer
 */
public class ProtobufRpcServer {

    public static void main(String[] args) throws IOException {
        // 请开始你的表演!
        Configuration conf = new Configuration();
        String hostname = "localhost";
        int port = 9998;
        RPC.setProtocolEngine(conf, MyResourceTrackerPB.class, ProtobufRpcEngine.class);

        //  注释: 构建 Rpc Server
        RPC.Server  server = new RPC.Builder(conf)
                .setProtocol(MyResourceTrackerPB.class)
                .setInstance((BlockingService) MyResourceTracker.MyResourceTrackerService
                        .newReflectiveBlockingService(new MyResourceTrackerServerSidePB(new 
MyResourceTrackerService())))
                .setBindAddress(hostname)
                .setPort(port)
                .setNumHandlers(1)
                .setVerbose(true)
                .build();

        //  注释: Rpc Server 启动
        server.start();
    }
}

第九步: 编写 RPC Client 实现

package com.mazh.rpc.protobuf.demo2.client;

import com.google.protobuf.ServiceException;
import com.mazh.rpc.protobuf.demo2.MyResourceTrackerPB;
import com.mazh.rpc.protobuf.demo2.proto.MyResourceTrackerMessage;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ipc.RPC;

import java.io.IOException;
import java.net.InetSocketAddress;

/*************************************************
 *  注释: Rpc Client 端实现
 */
public class ProtobufRpcClient {

    public static void main(String[] args) throws IOException {
        // 请开始你的表演!

        //  注释: 设置 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 bigdata02 = 
builder.setHostname("bigdata02").setCpu(64)
                .setMemory(128).build();

        //  注释: 发送 RPC 请求,获取响应
        MyResourceTrackerMessage.MyRegisterNodeManagerResponseProto response = null;
        try {
            response = protocolProxy
                    .registerNodeManager(null, bigdata02);
        } catch(ServiceException e) {
            e.printStackTrace();
        }

        //  注释: 处理响应
        String flag = response.getFlag();
        System.out.println("最终注册结果: flag = " + flag);
    }
}

第十步:测试 服务端的执行结果:

NodeManager 的注册消息: hostname = bigdata02, cpu = 64, memory = 128

客户端的执行结果:

最终注册结果: flag = true 本文内容到此结束,主要讲解了RPC网络通信架构设计,后续内容请继续关注

本人是该领域的小白,在学习的路上,上述文章如有错误还请指出批评。