之前我们写了Akka的RPC通信,那么为什么Spark底层要这样设计呢?
因为以后我们要玩的是分布式并行计算,我们需要将任务提交到集群上执行。那么这个任务在Master上执行还是在Worker上执行?答案是在Worker上执行,严格的来说是在Worker的子进程上执行,就好比我们之前使用的yarn,yarn上有nodemanager,而任务并不在nodemanager上执行,而是在nodemanager启动的yarnchild上执行。Worker一开始就将自己的资源注册到Master上,然后Master就知道哪些Worker上有资源。然后Master就将计算任务分配到某台或某几台Worker上。
在spark2.0已经将Akka移除,因为Akka的扩展性并不是很好,于是又用Netty重新写了一套。
接下来我们来看看Netty的思想:
什么是NIO,我们之前写的Socket就是传统的IO,传统的IO是有问题的。NIO就是new IO(新IO)它的异步性要比传统IO强大。
接下来我们开始写Netty的RPC:
我们要写NIO的话就要调用Netty的类的方法。我们首先配置服务端的线程池组,为什么Netty的性能高呢?因为它首先就创建线程池,以后这个线程池可以供Socket连接,Socket通信使用。
我们的服务端是有两个线程池的。客户端有一个线程池就够了。
先说服务端,第一个线程池是用来接收客户端的连接的,作为一个服务端,一定会有很多的客户端要和他进行连接。而建立连接是要启动线程的,如果没有线程池,我们还需要自己创建一个线程。有线程池的话,我们只需要从线程池中取一个跟他建立连接就好了。
第二个线程池是服务端与客户端进行Socket通信的时候使用的。用于用户进行SocketChannel的网络读写。
如果让我们自己去写线程池的话,一定会写很多代码,而现在我们只需要使用Netty提供的工具类就行。
接下来我们要new 一个ServerBootstrap它也是一个工具类。它是Netty用户启动NIO服务端的辅助启动类,可以降低服务端的开发复杂度。
这个工具类首先需要传入线程池,然后创建NioServerSocketCannel,要创建的话我们需要传进去一个实现类。这里的classOf相当于.class,传进去之后通过这个类的字节码进行反射。然后再绑定I/O事件处理类。这个I/O事件处理类就可以处理我们的读写请求。这里的pipeline的意思是管道,或者流水线。再这里可以添加很多的处理器,而处理器是按照先后顺序执行的。
那么我们自己写的处理器类只要添加进去Netty就能认识吗?肯定是不行的,他一定要实现一个接口或继承一个类才能被识别。那么我们这里继承的是ChannelInboundHandlerAdapter,继承这个类之后还要重写几个关键的方法。
channelActive:客户端与服务端建立连接之后,这个方法会被调用。
channelRead:客户端如果给服务端发消息,这个方法就会被调用。
channelReadComplete:服务端接收完来自客户端的消息之后,再发回消息给客户端。
exceptionCaught:当出现异常的时候,这个方法会被调用。
那么也就是说,如果我们使用了Netty之后,我们只需要写处理器中的逻辑就行了,其他的都是固定的套路。
前面的操作只是给ServerBootstrap绑定了一些必要的条件,接下来的
bootstrap.bind(host,port).sync()才是真正的启动
我们还需要使用
channelFuture.channel().closeFuture().sync()让服务等待关闭命令。
结束之后我们需要释放进程池资源。
服务端说完了,接下来说客户端:
客户端只有一个线程池,因为她以后只负责和服务端发消息即可。而连接请求交给服务端即可。
客户端的辅助启动类为Bootstrap这个工具类传入线程池之后,需要创建NioSocketChannel,接着同样需要绑定I/O事件处理类。
ctx.writeAndFlush(Unpooled.copiedBuffer(content.getBytes("utf-8")))
这句话是在客户端连接到服务端之后,客户端给服务端发消息用的Unpooled.copiedBuffer将字节数组转换为ByteBuf类型。这里我们不仅可以写字节缓冲,我们还可以写case class
服务端接收到消息之后:在ChannelRead方法中得到的是一个Any类型的消息,我们需要使用asInstanceOf将他强转为ByteBuf
ByteBuf中放的是字节数组,所以我们要想办法将他拿出来。
byteBuf.readableBytes()
这个方法是读出ByteBuf中字节数组的大小。然后我们创建出同样大小的空Byte数组
byteBuf.readBytes(b)
这样就可以将ByteBuf中的字节数组中的值全部交给我们创建的字节数组
转为字符串输出。
接着在这个方法中服务端也可以以同样的方式发送消息给客户端。
channelRead方法执行完之后ChannelReadComplete方法会被调用
至此我们就可以利用Netty实现简单的字符串通信了。通信的时候主机名和端口号保持一致
以下贴出代码:
服务端:
package com.test.server
import com.test.handler.ServerHandler
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.{ChannelInitializer, nio}
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
class NettyServer {
def bind(host:String,port:Int): Unit ={
//配置服务端线程池组
//用于服务端接收客户端的连接。
val bossGroup = new NioEventLoopGroup()
//用户进行SocketChannel的网络读写
val workerGroup = new nio.NioEventLoopGroup()
//是netty用户启动NIO服务端的辅助启动类,降低服务端的开发复杂度
try {
val bootstrap = new ServerBootstrap()
//将两个线程池组作为参数传入ServerBootstrap
bootstrap.group(bossGroup, workerGroup)
//创建NioServerSocketChannel
.channel(classOf[NioServerSocketChannel])
//绑定I/O事件处理类
.childHandler(new ChannelInitializer[SocketChannel] {
override def initChannel(ch: SocketChannel): Unit = {
ch.pipeline().addLast(
new ServerHandler
)
}
})
//绑定端口,调用sync方法等待绑定操作完成
val channelFuture = bootstrap.bind(host, port).sync()
//等待服务关闭
channelFuture.channel().closeFuture().sync()
}finally{
bossGroup.shutdownGracefully()
workerGroup.shutdownGracefully()
}
}
}
object NettyServer{
def main(args: Array[String]): Unit = {
val host = args(0)
val port = args(1).toInt
val server = new NettyServer
server.bind(host,port)
}
}
服务端的事件处理类:
package com.test.handler
import java.nio.ByteBuffer
import io.netty.buffer.{ByteBuf, Unpooled}
import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandler, ChannelInboundHandlerAdapter}
class ServerHandler extends ChannelInboundHandlerAdapter{
//客户端与服务端建立连接之后会被调用
override def channelActive(ctx: ChannelHandlerContext): Unit = {
println("一个客户端连接上了")
}
//客户端给服务端发消息,这个方法就会被调用
override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
println("收到客户端发来的消息了")
val byteBuf = msg.asInstanceOf[ByteBuf]
val b = new Array[Byte](byteBuf.readableBytes())
byteBuf.readBytes(b)
val str: String = new String(b,"utf-8")
println(str)
val back = "在ChannelRead中写回数据给客户端"
ctx.write(Unpooled.copiedBuffer(back.getBytes()))
}
//服务端发回消息给客户端,将消息队列中的数据写入到SocketChannel并发送给对方
override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
println("channelReadComplete invoke")
ctx.flush()
}
override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
println("exceptionCaught invoked")
ctx.close()
}
}
客户端:
package com.test.client
import com.test.handler.ClientHandler
import com.test.server.NettyServer
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.{NioEventLoop, NioEventLoopGroup}
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
class NettyClient {
def connect(host:String,port:Int): Unit ={
//创建客户端NIO线程池
val eventGroup = new NioEventLoopGroup()
//创建客户端辅助启动类
val bootstrap = new Bootstrap
try{
println("进入try")
bootstrap.group(eventGroup)
.channel(classOf[NioSocketChannel])
.handler(new ChannelInitializer[SocketChannel] {
override def initChannel(ch: SocketChannel): Unit = {
ch.pipeline().addLast(
new ClientHandler
)
}
})
val channelFuture = bootstrap.connect(host,port).sync()
channelFuture.channel().closeFuture().sync()
}finally {
eventGroup.shutdownGracefully()
}
}
}
object NettyClient{
def main(args: Array[String]): Unit = {
val host = args(0)
val port = args(1).toInt
val client = new NettyClient
client.connect(host,port)
}
}
客户端的事件处理类:
package com.test.handler
import io.netty.buffer.{ByteBuf, Unpooled}
import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter}
class ClientHandler extends ChannelInboundHandlerAdapter{
//客户端与服务端连接上之后会被调用
override def channelActive(ctx: ChannelHandlerContext): Unit = {
println("已经跟服务端连接上了")
val content = "客户端在ChannelActive发消息给服务端"
ctx.writeAndFlush(Unpooled.copiedBuffer(content.getBytes("utf-8")))
}
//服务端发来消息之后会被调用
override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
println("channelRead")
val buf: ByteBuf = msg.asInstanceOf[ByteBuf]
val bytes: Array[Byte] = new Array[Byte](buf.readableBytes())
buf.readBytes(bytes)
val str = new String(bytes,"utf-8")
println(str)
}
//客户端回复消息给客户端
override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
println("channelReadComplete")
}
override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
}
}