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