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 等组件暂不做深入了解,后续会一一剖析。

posted @ 2019-07-02 22:19  JohnnyBai  阅读(2008)  评论(0编辑  收藏  举报