大家好,我是易安!
Netty是一个高性能网络应用框架,利用十分广泛,目前在Java畛域里,Netty基本上成为网络程序的标配了。Netty框架功能丰富,也非常复杂,明天咱们次要剖析Netty框架中的线程模型,而 线程模型间接影响着网络程序的性能。
网络编程性能的瓶颈
BIO模型里,所有read()操作和write()操作都会阻塞以后线程的,如果客户端曾经和服务端建设了一个连贯,而迟迟不发送数据,那么服务端的read()操作会始终阻塞,所以 应用BIO模型,个别都会为每个socket调配一个独立的线程,这样就不会因为线程阻塞在一个socket上而影响对其余socket的读写。BIO的线程模型如下图所示,每一个socket都对应一个独立的线程;为了防止频繁创立、耗费线程,能够采纳线程池,然而socket和线程之间的对应关系并不会变动。
BIO的线程模型
BIO这种线程模型实用于socket连贯不是很多的场景;然而当初的互联网场景,往往须要服务器可能撑持十万甚至百万连贯,而创立十万甚至上百万个线程显然并不事实,所以BIO线程模型无奈解决百万连贯的问题。如果仔细观察,你会发现互联网场景中,尽管连贯多,然而每个连贯上的申请并不频繁,所以线程大部分工夫都在期待I/O就绪。也就是说线程大部分工夫都阻塞在那里,这齐全是节约,如果咱们可能解决这个问题,那就不须要这么多线程了。
顺着这个思路,咱们能够将线程模型优化为下图这个样子,能够用一个线程来解决多个连贯,这样线程的利用率就上来了,同时所需的线程数量也跟着降下来了。这个思路很好,可是应用BIO相干的API是无奈实现的,这是为什么呢?因为BIO相干的socket读写操作都是阻塞式的,而一旦调用了阻塞式API,在I/O就绪前,调用线程会始终阻塞,也就无奈解决其余的socket连贯了。
现实的线程模型图
好在Java里还提供了非阻塞式(NIO)API, 利用非阻塞式API就可能实现一个线程解决多个连贯了。那具体如何实现呢?当初广泛都是 采纳Reactor模式,包含Netty的实现。所以,要想了解Netty的实现,接下来咱们就须要先理解一下Reactor模式。
Reactor模式
上面是Reactor模式的类结构图,其中Handle指的是I/O句柄,在Java网络编程里,它实质上就是一个网络连接。Event Handler很容易了解,就是一个事件处理器,其中handle\_event()办法解决I/O事件,也就是每个Event Handler解决一个I/O Handle;get\_handle()办法能够返回这个I/O的Handle。Synchronous Event Demultiplexer能够了解为操作系统提供的I/O多路复用API,例如POSIX规范里的select()以及Linux外面的epoll()。
Reactor模式类结构图
Reactor模式的外围天然是 Reactor这个类,其中register\_handler()和remove\_handler()这两个办法能够注册和删除一个事件处理器; handle\_events()形式是外围,也是Reactor模式的发动机,这个办法的外围逻辑如下:首先通过同步事件多路选择器提供的select()办法监听网络事件,当有网络事件就绪后,就遍历事件处理器来解决该网络事件。因为网络事件是源源不断的,所以在主程序中启动Reactor模式,须要以 while(true){}
的形式调用handle\_events()办法。
void Reactor::handle_events(){ //通过同步事件多路选择器提供的 //select()办法监听网络事件 select(handlers); //解决网络事件 for(h in handlers){ h.handle_event(); }}// 在主程序中启动事件循环while (true) { handle_events(); }
Netty的线程模型
Netty的实现尽管参考了Reactor模式,然而并没有齐全照搬, Netty中最外围的概念是事件循环(EventLoop),其实也就是Reactor模式中的Reactor, 负责监听网络事件并调用事件处理器进行解决。在4.x版本的Netty中,网络连接和EventLoop是稳固的多对1关系,而EventLoop和Java线程是1对1关系,这里的稳固指的是关系一旦确定就不再发生变化。也就是说一个网络连接只会对应惟一的一个EventLoop,而一个EventLoop也只会对应到一个Java线程,所以 一个网络连接只会对应到一个Java线程。
一个网络连接对应到一个Java线程上,有什么益处呢?最大的益处就是对于一个网络连接的事件处理是单线程的,这样就 防止了各种并发问题。
Netty中的线程模型能够参考下图,外围指标是用一个线程解决多个网络连接。
Netty中的线程模型
Netty中还有一个外围概念是 EventLoopGroup,顾名思义,一个EventLoopGroup由一组EventLoop组成。理论应用中,个别都会创立两个EventLoopGroup,一个称为bossGroup,一个称为workerGroup。为什么会有两个EventLoopGroup呢?
这个和socket解决网络申请的机制无关,socket解决TCP网络连接申请,是在一个独立的socket中,每当有一个TCP连贯胜利建设,都会创立一个新的socket,之后对TCP连贯的读写都是由新创建解决的socket实现的。也就是说 解决TCP连贯申请和读写申请是通过两个不同的socket实现的。下面咱们在探讨网络申请的时候,为了简化模型,只是探讨了读写申请,而没有探讨连贯申请。
在Netty中,bossGroup就用来解决连贯申请的,而workerGroup是用来解决读写申请的。bossGroup解决完连贯申请后,会将这个连贯提交给workerGroup来解决, workerGroup外面有多个EventLoop,那新的连贯会交给哪个EventLoop来解决呢?这就须要一个负载平衡算法,Netty中目前应用的是 轮询算法。
上面咱们用Netty从新实现以下echo程序的服务端,近距离感受一下Netty。
用Netty实现Echo程序服务端
上面的示例代码基于Netty实现了echo程序服务端:首先创立了一个事件处理器(等同于Reactor模式中的事件处理器),而后创立了bossGroup和workerGroup,再之后创立并初始化了ServerBootstrap,代码还是很简略的,不过有两个中央须要留神一下。
第一个,如果NettybossGroup只监听一个端口,那bossGroup只须要1个EventLoop就能够了,多了纯属节约。
第二个,默认状况下,Netty会创立“2*CPU核数”个EventLoop,因为网络连接与EventLoop有稳固的关系,所以事件处理器在解决网络事件的时候是不能有阻塞操作的,否则很容易导致申请大面积超时。如果切实无奈防止应用阻塞操作,那能够通过线程池来异步解决。
//事件处理器final EchoServerHandler serverHandler = new EchoServerHandler();//boss线程组EventLoopGroup bossGroup = new NioEventLoopGroup(1);//worker线程组EventLoopGroup workerGroup = new NioEventLoopGroup();try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch){ ch.pipeline().addLast(serverHandler); } }); //bind服务端端口 ChannelFuture f = b.bind(9090).sync(); f.channel().closeFuture().sync();} finally { //终止工作线程组 workerGroup.shutdownGracefully(); //终止boss线程组 bossGroup.shutdownGracefully();}//socket连贯处理器class EchoServerHandler extends ChannelInboundHandlerAdapter { //解决读事件 @Override public void channelRead( ChannelHandlerContext ctx, Object msg){ ctx.write(msg); } //解决读实现事件 @Override public void channelReadComplete( ChannelHandlerContext ctx){ ctx.flush(); } //解决异样事件 @Override public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }}
总结
Netty是一个款优良的网络编程框架,性能十分好,为了实现高性能的指标,Netty做了很多优化,例如优化了ByteBuffer、反对零拷贝等等,和并发编程相干的就是它的线程模型了。Netty的线程模型设计得很精美,每个网络连接都关联到了一个线程上,这样做的益处是:对于一个网络连接,读写操作都是单线程执行的,从而防止了并发程序的各种问题。
本文由mdnice多平台公布