• spark 源码分析之五--Spark RPC剖析之创建NettyRpcEnv


    在前面源码剖析介绍中,spark 源码分析之二 -- SparkContext 的初始化过程 中的SparkEnv和 spark 源码分析之四 -- TaskScheduler的创建和启动过程 中的ClientApp启动过程中,都涉及到了Spark的内置RPC的知识。本篇专门把RPC 拿出来剖析一下。

    因为RPC 在 Spark 中内容虽然不多,但理清楚还是花费很多精力的,计划每天只剖析一小部分,等剖析完毕,会专门有一篇总结性的文章出来。

    本篇作为RPC分析开篇,主要剖析了NettyRpcEnv创建的过程。

    Spark Rpc使用示例

    我们以 org.apache.spark.deploy.ClientApp#start 方法中的调用API创建 RPC 的过程入口。

    // 1. 创建 RPC Environment
    val rpcEnv = RpcEnv.create("driverClient", Utils.localHostName(), 0, conf, new SecurityManager(conf))

    创建NettyRpcEnv

    如下是创建NettyRpcEnv的时序图(画的不好看,见谅):

    RpcEnv是scala 的object伴生对象(本质上是一个java 单例对象),去调用NettyRpcEnvFactory去创建 NettyRpcEnv 对象,序列化使用的是java序列化内建的方式,然后调用Utils 类重试启动Server。启动成功后返回给用户。

    org.apache.spark.rpc.netty.NettyRpcEnv#startServer 代码如下:

     1 def startServer(bindAddress: String, port: Int): Unit = {
     2     val bootstraps: java.util.List[TransportServerBootstrap] =
     3       if (securityManager.isAuthenticationEnabled()) {
     4         java.util.Arrays.asList(new AuthServerBootstrap(transportConf, securityManager))
     5       } else {
     6         java.util.Collections.emptyList()
     7       }
     8     server = transportContext.createServer(bindAddress, port, bootstraps)
     9     dispatcher.registerRpcEndpoint(
    10       RpcEndpointVerifier.NAME, new RpcEndpointVerifier(this, dispatcher))
    11   }

    在TransportServer构造过程中调用了init方法。org.apache.spark.network.server.TransportServer#init 源码如下:

     1 private void init(String hostToBind, int portToBind) {
     2 
     3   IOMode ioMode = IOMode.valueOf(conf.ioMode());
     4   EventLoopGroup bossGroup =
     5     NettyUtils.createEventLoop(ioMode, conf.serverThreads(), conf.getModuleName() + "-server");
     6   EventLoopGroup workerGroup = bossGroup;
     7 
     8   PooledByteBufAllocator allocator = NettyUtils.createPooledByteBufAllocator(
     9     conf.preferDirectBufs(), true /* allowCache */, conf.serverThreads());
    10 
    11   bootstrap = new ServerBootstrap()
    12     .group(bossGroup, workerGroup)
    13     .channel(NettyUtils.getServerChannelClass(ioMode))
    14     .option(ChannelOption.ALLOCATOR, allocator)
    15     .option(ChannelOption.SO_REUSEADDR, !SystemUtils.IS_OS_WINDOWS)
    16     .childOption(ChannelOption.ALLOCATOR, allocator);
    17 
    18   this.metrics = new NettyMemoryMetrics(
    19     allocator, conf.getModuleName() + "-server", conf);
    20 
    21   if (conf.backLog() > 0) {
    22     bootstrap.option(ChannelOption.SO_BACKLOG, conf.backLog());
    23   }
    24 
    25   if (conf.receiveBuf() > 0) {
    26     bootstrap.childOption(ChannelOption.SO_RCVBUF, conf.receiveBuf());
    27   }
    28 
    29   if (conf.sendBuf() > 0) {
    30     bootstrap.childOption(ChannelOption.SO_SNDBUF, conf.sendBuf());
    31   }
    32 
    33   bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    34     @Override
    35     protected void initChannel(SocketChannel ch) {
    36       RpcHandler rpcHandler = appRpcHandler;
    37       for (TransportServerBootstrap bootstrap : bootstraps) {
    38         rpcHandler = bootstrap.doBootstrap(ch, rpcHandler);
    39       }
    40       context.initializePipeline(ch, rpcHandler);
    41     }
    42   });
    43 
    44   InetSocketAddress address = hostToBind == null ?
    45       new InetSocketAddress(portToBind): new InetSocketAddress(hostToBind, portToBind);
    46   channelFuture = bootstrap.bind(address);
    47   channelFuture.syncUninterruptibly();
    48 
    49   port = ((InetSocketAddress) channelFuture.channel().localAddress()).getPort();
    50   logger.debug("Shuffle server started on port: {}", port);
    51 }

    主要功能是:调用netty API 初始化 nettyServer。

    org.apache.spark.rpc.netty.Dispatcher#registerRpcEndpoint的源码如下:

     1 def registerRpcEndpoint(name: String, endpoint: RpcEndpoint): NettyRpcEndpointRef = {
     2   val addr = RpcEndpointAddress(nettyEnv.address, name)
     3   val endpointRef = new NettyRpcEndpointRef(nettyEnv.conf, addr, nettyEnv)
     4   synchronized {
     5     if (stopped) {
     6       throw new IllegalStateException("RpcEnv has been stopped")
     7     }
     8     if (endpoints.putIfAbsent(name, new EndpointData(name, endpoint, endpointRef)) != null) {
     9       throw new IllegalArgumentException(s"There is already an RpcEndpoint called $name")
    10     }
    11     val data = endpoints.get(name)
    12     endpointRefs.put(data.endpoint, data.ref)
    13     receivers.offer(data)  // for the OnStart message
    14   }
    15   endpointRef
    16 }

    EndpointData 在初始化过程中会放入 OnStart 消息。
    在 Inbox 的 process 中,有如下代码:

    1 case OnStart =>
    2   endpoint.onStart()
    3   if (!endpoint.isInstanceOf[ThreadSafeRpcEndpoint]) {
    4     inbox.synchronized {
    5       if (!stopped) {
    6         enableConcurrent = true
    7       }
    8     }
    9   }

    调用 endpoint 的 onStart 方法和 初始化 是否支持并发处理模式。endpoint 指的是 RpcEndpointVerifier, 其 onStart 方法如下:

    1 /**
    2    * Invoked before [[RpcEndpoint]] starts to handle any message.
    3    */
    4   def onStart(): Unit = {
    5     // By default, do nothing.
    6   }

    即不做任何事情,直接返回,至此初始化NettyRPCEnv 流程就剖析完。伴生对象RpcEnv调用netty rpc 工厂创建NettyRpcEnv 对象,然后使用重试机制启动TransportServer,然后NettyRpcEnv注册RpcEndpointVerifier

    到Dispatcher。最终返回 NettyRpcEnv 给API调用端,NettyRpcEnv 创建成功。在这里,Dispatcher 和 TransportServer 等组件暂不做深入了解,后续会一一剖析。

  • 相关阅读:
    C# 判断一个文本文件的编码格式(转载)
    img图片加载出错处理(转载)
    C#中当程序的访问权限不足时,Directory.Exists和File.Exists方法不会抛出异常报错
    ajax上传文件以及实现上传进度条(转载)
    通过jQuery Ajax使用FormData对象上传文件 (转载)
    CMD命令查看当前电脑安装所有版本.NET Core SDK(转载)
    SQL Server聚合函数与聚合开窗函数 (转载)
    C#中,使用显式类型转换(int)和Math.Round方法,将浮点数转换为整数的区别
    SQLServer 窗口函数(转载)
    node-webkit教程(8)Platform Service之Clipboard
  • 原文地址:https://www.cnblogs.com/johnny666888/p/11122907.html
Copyright © 2020-2023  润新知