Netty的基本概念
使用Netty(NIO框架)完成一次RPC通信,相比传统基于Java序列化+BIO的RPC通信框架,其通信性能可以提升8倍多。
传统RPC调用性能差的原因
1、网络传输方式存在弊端
传统的RPC框架都是采用BIO,当客户端的并发压力或网络时延增大时,BIO会因频繁的“wait”导致I/O线程经常出现阻塞的情况,线程本身无法高效地工作,I/O处理能力自然下降。
2、序列化方式存在弊端
Java序列化是针对对象(Object)设计的编解码技术,无法跨语言使用。因此无法支持在异构系统之间对接,无法将Java序列化后的字节码流反序列化成原始对象。
相比其他开源的序列化框架,Java序列化后的字节码流占用的空间太大,无论是传输还是持久化到磁盘,都会增加资源的消耗。
序列化性能差,在编解码过程中需要占用更高的CPU资源。
3、线程模型存在弊端
BIO模型使得每个TCP连接都需要分配1个线程,而线程资源是JVM宝贵的资源,当I/O读写阻塞导致线程无法及时释放时,系统性能急剧下降,甚至导致JVM无法创建新线程。
Netty高性能原因
1、I/O传输模型
I/O传输模型在很大程度上决定了框架的性能。Netty使用了NIO模型
2、数据协议
一般来说内部私有协议比公有协议的性能更高。
3、线程模型
线程模型设计如何读取数据包,读取之后的编解码在哪个线程中进行,编解码后消息如何派发等。
1、Reactor线程模型与NioEventLoopGroup
Netty是Reactor模型的一个实现。Reactor的线程模型有三种:单线程模型,多线程模型,主从多线程模型。
1.1 单线程模型
单线程模型,即Acceptor的处理和Handler的处理都在同一个线程中。这种模型弊端是:当其中某个Handler阻塞时,会导致其他所有的Handler都无法执行,也会导致整个服务不能接收新的Client请求,因为Acceptor也被阻塞了。因此这种模型应用较少。
单线程模型在Netty中的应用代码如下。
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup);
上面代码中,首先实例化一个NioEventLoopGroup,然后调用server.group(bossGroup),设置服务端的EventLoopGroup 。在启动服务端的Netty程序时,需要设置bossGroup和workerGroup,这里只设置一个bossGroup,原因是ServerBootstrap重写了group方法,代码如下。当传入一个group时,bossGroup和workerGroup是同一个NioEventLoopGroup,并且由于NioEventLoopGroup只设置一个线程,因此Netty的Acceptor和后续的客户端连接的I/O操作都是在一个线程中处理的。因此这样设置,对应的就是Reactor的单线程模型。
public ServerBootstrap group(EventLoopGroup group) {
return this.group(group, group);
}
1.2 多线程模型
多线程模型,设计一个专门的线程Acceptor用于监听客户端的TCP连接请求,I/O操作由特定的NIO进程池负责,每个客户端都和一个特定的NIO线程绑定,这个客户端的所有I/O操作都是在同一个线程中完成的。需要注意的是,NIO线程数较少,客户端连接很多时,一个NIO线程可以同时绑定多个客户端连接中。
多线程模型在Netty中的应用代码如下,只需要将bossGroup的参数设置为大于1的数,就是Reactor多线程模型。
EventLoopGroup bossGroup = new NioEventLoopGroup(128);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup);
1.3 主从多线程模型
一般情况下,Reactor的多线程模型已经适用于大部分业务场景,但是如果需要同时处理大量的客户端连接请求,或者需要在客户端连接时增加一些例如权限校验等操作,那么单个Acceptor有可能处理不过来,将会造成大量的客户端连接超时。
主从多线程模型,将服务端接收客户端的连接请求专门设计一个独立的线程池。
主从Reactor多线程模型在Netty的应用代码如下,bossGroup为主线程,而workerGroup的线程数是CPU核数×2,对应到主从Reactor多线程模型。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
2、服务端ServerBootstrap
1、服务端指定两个EventLoopGroup,一个是bossGroup,处理客户端的连接请求,另一个是workerGroup,用于处理与各个客户端连接的I/O操作。
2、指定Channel的类型,这里是服务端,所以使用NioServerSocketChannel,用于异步非阻塞的服务端TCP Socket连接。Channel是对Java底层Socket连接的抽象。
3、配置自定义的业务处理器Handler。
public class ChatServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("服务已启动,监听端口" + port + "");
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("服务已关闭");
}
}
public static void main(String[] args) {
try {
new ChatServer().start(20000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3 客户端Bootstrap
Bootstrap是Netty的工厂类,可以通过它完成客户端和服务端的Netty初始化。下面代码主要步骤是:
1、指定客户端的EventLoopGroup,无论是服务端还是客户端,都需要指定EventLoopGroup。
2、ChannelType,因为是客户端,因此指定NioSocketChannel
3、设置处理数据的Handler。
public class CharClient {
public CharClient connect(int port, String host, final String nickName) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
return this;
}
public static void main(String[] args) {
new CharClient().connect(8080, "loalhost", "demo");
}
}