来看上面这个图,当客户端发动一次Http申请时,服务端的解决流程时怎么样的?

简略来说能够分为以下几个步骤:

  1. 基于TCP协定建设网络通信。
  2. 开始向服务端端传输数据。
  3. 服务端承受到数据进行解析,开始解决本次申请逻辑。
  4. 服务端解决实现后返回后果给客户端。

在这个过程中,会波及到网络IO通信,在传统的BIO模式下,客户端向服务端发动一个数据读取申请,客户端在收到服务端返回数据之前,始终处于阻塞状态,直到服务端返回数据后实现本次会话。这个过程就叫同步阻塞IO,在BIO模型中如果想实现异步操作,就只能应用多线程模型,也就是一个申请对应一个线程,这样就可能防止服务端的链接被一个客户端占用导致连接数无奈进步。

同步阻塞IO次要体现在两个阻塞点

  • 服务端接管客户端连贯时的阻塞。
  • 客户端和服务端的IO通信时,数据未就绪的状况下的阻塞。

在这种传统BIO模式下,会造成一个十分重大的问题,如下图所示,如果同一时刻有N个客户端发动申请,依照BIO模型的特点,服务端在同一时刻只能解决一个申请。将导致客户端申请须要排队解决,带来的影响是,用户在期待一次申请解决返回的工夫十分长。意味着服务端没有并发解决能力,这显然不适合。

那么,服务端应该如何优化呢?

非阻塞IO

从后面的剖析发现,服务端在解决一次申请时,会处于阻塞状态无奈解决后续申请,那是否可能让被阻塞的中央优化成不阻塞呢?于是就有了非阻塞IO(NIO)

非阻塞IO,就是客户端向服务端发动申请时,如果服务端的数据未就绪的状况下, 客户端申请不会被阻塞,而是间接返回。然而有可能服务端的数据还未筹备好的时候,客户端收到的返回是一个空的, 那客户端怎么拿到最终的数据呢?

如图所示,客户端只能通过轮询的形式来取得申请后果。NIO相比BIO来说,少了阻塞的过程在性能和连接数上都会有明显提高。

NIO依然有一个弊病,就是轮询过程中会有很多空轮询,而这个轮询会存在大量的零碎调用(发动内核指令从网卡缓冲区中加载数据,用户空间到内核空间的切换),随着连贯数量的减少,会导致性能问题。

多路复用机制

I/O多路复用的实质是通过一种机制(零碎内核缓冲I/O数据),让单个过程能够监督多个文件描述符,一旦某个描述符就绪(个别是读就绪或写就绪),可能告诉程序进行相应的读写操作

什么是fd:在linux中,内核把所有的外部设备都当成是一个文件来操作,对一个文件的读写会调用内核提供的系统命令,返回一个fd(文件描述符)。而对于一个socket的读写也会有相应的文件描述符,成为socketfd。

常见的IO多路复用形式有【select、poll、epoll】,都是Linux API提供的IO复用形式,那么接下来重点讲一下select、和epoll这两个模型

  • select:过程能够通过把一个或者多个fd传递给select零碎调用,过程会阻塞在select操作上,这样select能够帮咱们检测多个fd是否处于就绪状态,这个模式有两个毛病

    • 因为他可能同时监听多个文件描述符,如果说有1000个,这个时候如果其中一个fd 处于就绪状态了,那么以后过程须要线性轮询所有的fd,也就是监听的fd越多,性能开销越大。
    • 同时,select在单个过程中能关上的fd是有限度的,默认是1024,对于那些须要反对单机上万的TCP连贯来说的确有点少
  • epoll:linux还提供了epoll的零碎调用,epoll是基于事件驱动形式来代替程序扫描,因而性能相对来说更高,次要原理是,当被监听的fd中,有fd就绪时,会告知以后过程具体哪一个fd就绪,那么以后过程只须要去从指定的fd上读取数据即可,另外,epoll所能反对的fd上线是操作系统的最大文件句柄,这个数字要远远大于1024
【因为epoll可能通过事件告知利用过程哪个fd是可读的,所以咱们也称这种IO为异步非阻塞IO,当然它是伪异步的,因为它还须要去把数据从内核同步复制到用户空间中,真正的异步非阻塞,应该是数据曾经齐全筹备好了,我只须要从用户空间读就行】

I/O多路复用的益处是能够通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得零碎在单线程的状况下能够同时解决多个客户端申请。它的最大劣势是零碎开销小,并且不须要创立新的过程或者线程,升高了零碎的资源开销,它的整体实现思维如图2-3所示。

客户端申请到服务端后,此时客户端在传输数据过程中,为了防止Server端在read客户端数据过程中阻塞,服务端会把该申请注册到Selector复路器上,服务端此时不须要期待,只须要启动一个线程,通过selector.select()阻塞轮询复路器上就绪的channel即可,也就是说,如果某个客户端连贯数据传输实现,那么select()办法会返回就绪的channel,而后执行相干的解决即可。

异步IO

异步IO和多路复用机制,最大的区别在于:当数据就绪后,客户端不须要发送内核指令从内核空间读取数据,而是零碎会异步把这个数据间接拷贝到用户空间,应用程序只须要间接应用该数据即可。

<center>图2-4 异步IO</center>

在Java中,咱们能够应用NIO的api来实现多路复用机制,实现伪异步IO。在网络通信演进模型剖析这篇文章中演示了Java API实现多路复用机制的代码,发现代码不仅仅繁琐,而且应用起来很麻烦。

所以Netty呈现了,Netty的I/O模型是基于非阻塞IO实现的,底层依赖的是JDK NIO框架的多路复用器Selector来实现。

一个多路复用器Selector能够同时轮询多个Channel,采纳epoll模式后,只须要一个线程负责Selector的轮询,就能够接入成千上万个客户端连贯。

Reactor模型

http://gee.cs.oswego.edu/dl/c...

理解了NIO多路复用后,就有必要再和大家说一下Reactor多路复用高性能I/O设计模式,Reactor实质上就是基于NIO多路复用机制提出的一个高性能IO设计模式,它的核心思想是把响应IO事件和业务解决进行拆散,通过一个或者多个线程来解决IO事件,而后将就绪失去事件散发到业务解决handlers线程去异步非阻塞解决,如图2-5所示。

Reactor模型有三个重要的组件:

  • Reactor :将I/O事件发派给对应的Handler
  • Acceptor :解决客户端连贯申请
  • Handlers :执行非阻塞读/写

<center>图2-5 Reactor模型</center>

这是最根本的单Reactor单线程模型(整体的I/O操作是由同一个线程实现的)

其中Reactor线程,负责多路拆散套接字,有新连贯到来触发connect 事件之后,交由Acceptor进行解决,有IO读写事件之后交给hanlder 解决。

Acceptor次要工作就是构建handler ,在获取到和client相干的SocketChannel之后 ,绑定到相应的hanlder上,对应的SocketChannel有读写事件之后,基于racotor 散发,hanlder就能够解决了(所有的IO事件都绑定到selector上,有Reactor散发)

Reactor 模式实质上指的是应用 I/O 多路复用(I/O multiplexing) + 非阻塞 I/O(non-blocking I/O) 的模式。

多线程单Reactor模型

单线程Reactor这种实现形式有存在着毛病,从实例代码中能够看出,handler的执行是串行的,如果其中一个handler解决线程阻塞将导致其余的业务解决阻塞。因为handler和reactor在同一个线程中的执行,这也将导致新的无奈接管新的申请,咱们做一个小试验:

  • 在上述Reactor代码的DispatchHandler的run办法中,减少一个Thread.sleep()。
  • 关上多个客户端窗口连贯到Reactor Server端,其中一个窗口发送一个信息后被阻塞,另外一个窗口再发信息时因为后面的申请阻塞导致后续申请无奈被解决。

为了解决这种问题,有人提出应用多线程的形式来解决业务,也就是在业务解决的中央退出线程池异步解决,将reactor和handler在不同的线程来执行,如图4-7所示。

<center>图2-6</center>

多线程多Reactor模型

在多线程单Reactor模型中,咱们发现所有的I/O操作是由一个Reactor来实现,而Reactor运行在单个线程中,它须要解决包含Accept()/read()/write/connect操作,对于小容量的场景,影响不大。然而对于高负载、大并发或大数据量的利用场景时,容易成为瓶颈,次要起因如下:

  • 一个NIO线程同时解决成千盈百的链路,性能上无奈撑持,即使NIO线程的CPU负荷达到100%,也无奈满足海量音讯的读取和发送;
  • 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连贯超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量音讯积压和解决超时,成为零碎的性能瓶颈;

所以,咱们还能够更进一步优化,引入多Reactor多线程模式,如图2-7所示,Main Reactor负责接管客户端的连贯申请,而后把接管到的申请传递给SubReactor(其中subReactor能够有多个),具体的业务IO解决由SubReactor实现。

Multiple Reactors 模式通常也能够等同于 Master-Workers 模式,比方 Nginx 和 Memcached 等就是采纳这种多线程模型,尽管不同的我的项目实现细节略有区别,但总体来说模式是统一的。

<center>图2-7</center>

  • Acceptor,申请接收者,在实际时其职责相似服务器,并不真正负责连贯申请的建设,而只将其申请委托 Main Reactor 线程池来实现,起到一个转发的作用。
  • Main Reactor,主 Reactor 线程组,次要负责连贯事件,并将IO读写申请转发到 SubReactor 线程池
  • Sub Reactor,Main Reactor 通常监听客户端连贯后会将通道的读写转发到 Sub Reactor 线程池中一个线程(负载平衡),负责数据的读写。在 NIO 中 通常注册通道的读(OP_READ)、写事件(OP_WRITE)。

高性能通信框架之Netty

在Java中,网络编程框架有很多,比方Java NIO、Mina、Netty、Grizzy等。然而在大家接触到的所有中间件中,绝大部分都是采纳Netty。

起因是Netty是目前最风行的一款高性能Java网络编程框架,它被宽泛援用在中间件、直播、社交、游戏等畛域。谈及到开源中间件,大家熟知的Dubbo、RocketMQ、Elasticsearch、Hbase、RocketMQ等都是采纳Netty实现。

在理论开发中,明天来听课的同学,99%的人都不会波及到应用Netty做网络编程开发,然而为什么还要花精力给大家讲呢?起因有几个

  • 在很多大厂面试的时候,会波及到相干的知识点

    • Netty高性能体现在哪些方面
    • Netty中有哪些重要组件
    • Netty的内存池、对象池的设计
  • 很多中间件都是用netty来做网络通信,那么咱们在剖析这些中间件的源码时,升高网络通信的了解难度
  • 晋升Java常识体系,尽可能的实现对技术体系了解的全面性。

为什么抉择Netty

Netty其实就是一个高性能NIO框架,所以它是基于NIO根底上的封装,实质上是提供高性能网络IO通信的性能。因为后面的课程中咱们曾经具体的对网络通信做了剖析,因而在学习Netty时,学习起来应该是更轻松的。

Netty提供了上述三种Reactor模型的反对,咱们能够通过Netty封装好的API来疾速实现不同Reactor模型的开发,这也是为什么大家都抉择Netty的起因之一,除此之外,Netty相比于NIO原生API,它有以下特点:

  • 提供了高效的I/O模型、线程模型和工夫解决机制
  • 提供了非常简单易用的API,相比NIO来说,针对根底的Channel、Selector、Sockets、Buffers等api提供了更高层次的封装,屏蔽了NIO的复杂性
  • 对数据协定和序列化提供了很好的反对
  • 稳定性,Netty修复了JDK NIO较多的问题,比方select空转导致的cpu耗费100%、TCP断线重连、keep-alive检测等问题。
  • 可扩展性在同类型的框架中都是做的十分好的,比方一个是可定制化的线程模型,用户能够在启动参数中抉择Reactor模型、 可扩大的事件驱动模型,将业务和框架的关注点拆散。
  • 性能层面的优化,作为网络通信框架,须要解决大量的网络申请,必然就面临网络对象须要创立和销毁的问题,这种对JVM的GC来说不是很敌对,为了升高JVM垃圾回收的压力,引入了两种优化机制

    • 对象池复用,
    • 零拷贝技术

Netty的生态介绍

首先,咱们须要去理解Netty到底提供了哪些性能,如图2-1所示,示意Netty生态中提供的性能阐明。后续内容中会逐渐的剖析这些性能。

<center>图2-1 Netty性能生态</center>

Netty的根本应用

须要阐明一下,咱们解说的Netty版本是4.x版本,之前有一段时间netty公布了一个5.x版本,然而被官网舍弃了,起因是:应用ForkJoinPool减少了复杂性,并且没有显示出显著的性能劣势。同时放弃所有的分支同步是相当多的工作,没有必要。

增加jar包依赖

应用4.1.66版本

<dependency>    <groupId>io.netty</groupId>    <artifactId>netty-all</artifactId></dependency>

创立Netty Server服务

大部分场景中,咱们应用的主从多线程Reactor模型,Boss线程是住Reactor,Worker是从Reactor。他们别离应用不同的NioEventLoopGroup

主Reactor负责解决Accept,而后把Channel注册到从Reactor,从Reactor次要负责Channel生命周期内的所有I/O事件。

public class NettyBasicServerExample {    public void bind(int port){        // 咱们要创立两个EventLoopGroup,        // 一个是boss专门用来接管连贯,能够了解为解决accept事件,        // 另一个是worker,能够关注除了accept之外的其它事件,解决子工作。        //下面留神,boss线程个别设置一个线程,设置多个也只会用到一个,而且多个目前没有利用场景,        // worker线程通常要依据服务器调优,如果不写默认就是cpu的两倍。        EventLoopGroup bossGroup=new NioEventLoopGroup();        EventLoopGroup workerGroup=new NioEventLoopGroup();        try {            //服务端要启动,须要创立ServerBootStrap,            // 在这外面netty把nio的模板式的代码都给封装好了            ServerBootstrap bootstrap = new ServerBootstrap();            bootstrap.group(bossGroup, workerGroup) //配置boss和worker线程                //配置Server的通道,相当于NIO中的ServerSocketChannel                .channel(NioServerSocketChannel.class)                //childHandler示意给worker那些线程配置了一个处理器,                // 配置初始化channel,也就是给worker线程配置对应的handler,当收到客户端的申请时,调配给指定的handler解决                .childHandler(new ChannelInitializer<SocketChannel>() {                    @Override                    protected void initChannel(SocketChannel socketChannel) throws Exception {                        socketChannel.pipeline().addLast(new NormalMessageHandler()); //增加handler,也就是具体的IO事件处理器                    }                });            //因为默认状况下是NIO异步非阻塞,所以绑定端口后,通过sync()办法阻塞直到连贯建设            //绑定端口并同步期待客户端连贯(sync办法会阻塞,直到整个启动过程实现)            ChannelFuture channelFuture=bootstrap.bind(port).sync();            System.out.println("Netty Server Started,Listening on :"+port);            //期待服务端监听端口敞开            channelFuture.channel().closeFuture().sync();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            //开释线程资源            bossGroup.shutdownGracefully();            workerGroup.shutdownGracefully();        }    }    public static void main(String[] args) {        new NettyBasicServerExample().bind(8080);    }}

上述代码阐明如下:

  • EventLoopGroup,定义线程组,相当于咱们之前在写NIO代码时定义的线程。这里定义了两个线程组别离是boss线程和worker线程,boss线程负责接管连贯,worker线程负责解决IO事件。boss线程个别设置一个线程,设置多个也只会用到一个,而且多个目前没有利用场景。而worker线程通常要依据服务器调优,如果不写默认就是cpu的两倍。
  • ServerBootstrap,服务端要启动,须要创立ServerBootStrap,在这外面netty把nio的模板式的代码都给封装好了。
  • ChannelOption.SO_BACKLOG

设置Channel类型

NIO模型是Netty中最成熟也是被宽泛援用的模型,因而在应用Netty的时候,咱们会采纳NioServerSocketChannel作为Channel类型。

bootstrap.channel(NioServerSocketChannel.class);

除了NioServerSocketChannel以外,还提供了

  • EpollServerSocketChannel,epoll模型只有在linux kernel 2.6以上能力反对,在windows和mac都是不反对的,如果设置Epoll在window环境下运行会报错。
  • OioServerSocketChannel,用于服务端阻塞地接管TCP连贯
  • KQueueServerSocketChannel,kqueue模型,是Unix中比拟高效的IO复用技术,常见的IO复用技术有select, poll, epoll以及kqueue等等。其中epoll为Linux独占,而kqueue则在许多UNIX零碎上存在。

注册ChannelHandler

在Netty中能够通过ChannelPipeline注册多个ChannelHandler,该handler就是给到worker线程执行的处理器,当IO事件就绪时,会依据这里配置的Handler进行调用。

这里能够注册多个ChannelHandler,每个ChannelHandler各司其职,比方做编码和解码的handler,心跳机制的handler,音讯解决的handler等。这样能够实现代码的最大化复用。

.childHandler(new ChannelInitializer<SocketChannel>() {    @Override    protected void initChannel(SocketChannel socketChannel) throws Exception {        socketChannel.pipeline().addLast(new NormalMessageHandler());    }});

ServerBootstrap中的childHandler办法须要注册一个ChannelHandler,这里配置了一个ChannelInitializer的实现类,通过实例化ChannelInitializer来配置初始化Channel。

当收到IO事件后,这个数据会在这多个handler中进行流传。上述代码中配置了一个NormalMessageHandler,用来接管客户端音讯并输入。

绑定端口

实现Netty的根本配置后,通过bind()办法真正触发启动,而sync()办法会阻塞,直到整个启动过程实现。

ChannelFuture channelFuture=bootstrap.bind(port).sync();

NormalMessageHandler

ServerHandler继承了ChannelInboundHandlerAdapter,这是netty中的一个事件处理器,netty中的处理器分为Inbound(进站)和Outbound(出站)处理器,前面会具体介绍。

public class NormalMessageHandler extends ChannelInboundHandlerAdapter {    //channelReadComplete办法示意音讯读完了的解决,writeAndFlush办法示意写入并发送音讯    @Override    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {        //这里的逻辑就是所有的音讯读取结束了,在对立写回到客户端。Unpooled.EMPTY_BUFFER示意空音讯,addListener(ChannelFutureListener.CLOSE)示意写完后,就敞开连贯        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);    }    //exceptionCaught办法就是产生异样的解决    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {        cause.printStackTrace();        ctx.close();    }    //channelRead办法示意读到音讯当前如何解决,这里咱们把音讯打印进去    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        ByteBuf in=(ByteBuf) msg;        byte[] req=new byte[in.readableBytes()];        in.readBytes(req); //把数据读到byte数组中        String body=new String(req,"UTF-8");        System.out.println("服务器端收到音讯:"+body);        //写回数据        ByteBuf resp=Unpooled.copiedBuffer(("receive message:"+body+"").getBytes());        ctx.write(resp);        //ctx.write示意把音讯再发送回客户端,然而仅仅是写到缓冲区,没有发送,flush才会真正写到网络下来    }}

通过上述代码发现,咱们只须要通过极少的代码就实现了NIO服务端的开发,相比传统的NIO原生类库的服务端,代码量大大减少,开发难度也大幅度降低。

Netty和NIO的api对应

TransportChannel ----对应NIO中的channel

EventLoop---- 对应于NIO中的while循环

EventLoopGroup: 多个EventLoop,就是事件循环

ChannelHandler和ChannelPipeline---对应于NIO中的客户逻辑实现handleRead/handleWrite(interceptor pattern)

ByteBuf---- 对应于NIO 中的ByteBuffer

Bootstrap 和 ServerBootstrap ---对应NIO中的Selector、ServerSocketChannel等的创立、配置、启动等

Netty的整体工作机制

Netty的整体工作机制如下,整体设计就是后面咱们讲过的多线程Reactor模型,拆散申请监听和申请解决,通过多线程别离执行具体的handler。

<center>图2-2</center>

网络通信层

网络通信层次要的职责是执行网络的IO操作,它反对多种网络通信协定和I/O模型的链接操作。当网络数据读取到内核缓冲区后,会触发读写事件,这些事件在分发给工夫调度器来进行解决。

在Netty中,网络通信的外围组件以下三个组件

  • Bootstrap, 客户端启动api,用来链接近程netty server,只绑定一个EventLoopGroup
  • ServerBootStrap,服务端监听api,用来监听指定端口,会绑定两个EventLoopGroup, bootstrap组件能够十分方便快捷的启动Netty应用程序
  • Channel,Channel是网络通信的载体,Netty本人实现的Channel是以JDK NIO channel为根底,提供了更高层次的形象,同时也屏蔽了底层Socket的复杂性,为Channel提供了更加弱小的性能。

如图2-3所示,示意的是Channel的罕用实现实现类关系图,AbstractChannel是整个Channel实现的基类,派生出了AbstractNioChannel(非阻塞io)、AbstractOioChannel(阻塞io),每个子类代表了不同的I/O模型和协定类型。

<center>图2-3 Channel的类关系图</center>

随着连贯和数据的变动,Channel也会存在多种状态,比方连贯建设、连贯注册、连贯读写、连贯销毁。随着状态的变动,Channel也会处于不同的生命周期,每种状态会绑定一个相应的事件回调。以下是常见的工夫回调办法。

  • channelRegistered, channel创立后被注册到EventLoop上
  • channelUnregistered,channel创立后未注册或者从EventLoop勾销注册
  • channelActive,channel处于就绪状态,能够被读写
  • channelInactive,Channel处于非就绪状态
  • channelRead,Channel能够从源端读取数据
  • channelReadComplete,Channel读取数据实现

简略总结一下,Bootstrap和ServerBootStrap别离负责客户端和服务端的启动,Channel是网络通信的载体,它提供了与底层Socket交互的能力。

而当Channel生命周期中的事件变动,就须要触发进一步解决,这个解决是由Netty的事件调度器来实现。

事件调度器

事件调度器是通过Reactor线程模型对各类事件进行聚合解决,通过Selector主循环线程集成多种事件(I/O工夫、信号工夫),当这些事件被触发后,具体针对该事件的解决须要给到服务编排层中相干的Handler来解决。

事件调度器外围组件:

  • EventLoopGroup。相当于线程池
  • EventLoop。相当于线程池中的线程

EventLoopGroup实质上是一个线程池,次要负责接管I/O申请,并调配线程执行解决申请。为了更好的了解EventLoopGroup、EventLoop、Channel之间的关系,咱们来看图2-4所示的流程。

<center>图2-4,EventLoop的工作机制</center>

从图中可知

  • 一个EventLoopGroup能够蕴含多个EventLoop,EventLoop用来解决Channel生命周期内所有的I/O事件,比方accept、connect、read、write等
  • EventLoop同一时间会与一个线程绑定,每个EventLoop负责解决多个Channel
  • 每新建一个Channel,EventLoopGroup会抉择一个EventLoop进行绑定,该Channel在生命周期内能够对EventLoop进行屡次绑定和解绑。

图2-5示意的是EventLoopGroup的类关系图,能够看出Netty提供了EventLoopGroup的多种实现,如NioEventLoop、EpollEventLoop、NioEventLoopGroup等。

从图中能够看到,EventLoop是EventLoopGroup的子接口,咱们能够把EventLoop等价于EventLoopGroup,前提是EventLoopGroup中只蕴含一个EventLoop。

<img src="https://mic-blob-bucket.oss-cn-beijing.aliyuncs.com/202111090024225.png" alt="image-20210812221329760" style="zoom:80%;" />

<center>图2-5 EventLoopGroup类关系图</center>

EventLoopGroup是Netty的外围解决引擎,它和后面咱们解说的Reactor线程模型有什么关系呢?其实,咱们能够简略的把EventLoopGroup当成是Netty中Reactor线程模型的具体实现,咱们能够通过配置不同的EventLoopGroup使得Netty反对多种不同的Reactor模型。

  • 单线程模型,EventLoopGroup只蕴含一个EventLoop,Boss和Worker应用同一个EventLoopGroup。
  • 多线程模型:EventLoopGroup蕴含多个EventLoop,Boss和Worker应用同一个EventLoopGroup。
  • 主从多线程模型:EventLoopGroup蕴含多个EventLoop,Boss是主Reactor,Worker是从Reactor模型。他们别离应用不同的EventLoopGroup,主Reactor负责新的网络连接Channel的创立(也就是连贯的事件),主Reactor收到客户端的连贯后,交给从Reactor来解决。

服务编排层

服务编排层的职责是负责组装各类的服务,简略来说,就是I/O事件触发后,须要有一个Handler来解决,所以服务编排层能够通过一个Handler解决链来实现网络事件的动静编排和有序的流传。

它蕴含三个组件

  • ChannelPipeline,它采纳了双向链表将多个Channelhandler链接在一起,当I/O事件触发时,ChannelPipeline会顺次调用组装好的多个ChannelHandler,实现对Channel的数据处理。ChannelPipeline是线程平安的,因为每个新的Channel都会绑定一个新的ChannelPipeline。一个ChannelPipeline关联一个EventLoop,而一个EventLoop只会绑定一个线程,如图2-6所示,示意ChannelPIpeline结构图。

    <img src="https://mic-blob-bucket.oss-cn-beijing.aliyuncs.com/202111090024172.png" alt="image-20210812223234507" style="zoom: 50%;" />

    <center>图2-6 ChannelPipeline</center>

    从图中能够看出,ChannelPipeline中蕴含入站ChannelInBoundHandler和出站ChannelOutboundHandler,前者是接收数据,后者是写出数据,其实就是InputStream和OutputStream,为了更好的了解,咱们来看图2-7。

<center>图2-7 InBound和OutBound的关系</center>

  • ChannelHandler, 针对IO数据的处理器,数据接管后,通过指定的Handler进行解决。
  • ChannelHandlerContext,ChannelHandlerContext用来保留ChannelHandler的上下文信息,也就是说,当事件被触发后,多个handler之间的数据,是通过ChannelHandlerContext来进行传递的。ChannelHandler和ChannelHandlerContext之间的关系,如图2-8所示。

    每个ChannelHandler都对应一个本人的ChannelHandlerContext,它保留了ChannelHandler所须要的上下文信息,多个ChannelHandler之间的数据传递,是通过ChannelHandlerContext来实现的。

<center>图2-8 ChannelHandler和ChannelHandlerContext关系</center>

以上就是Netty中外围的组件的个性和工作机制的介绍,后续的内容中还会具体的剖析这几个组件。能够看出,Netty的架构分层设计是十分正当的,它屏蔽了底层NIO以及框架层的实现细节,对于业务开发者来说,只须要关怀业务逻辑的编排和实现即可。

组件关系及原理总结

如图2-9所示,示意Netty中要害的组件协调原理,具体的工作机制形容如下。

  • 服务单启动初始化Boss和Worker线程组,Boss线程组负责监听网络连接事件,当有新的连贯建设时,Boss线程会把该连贯Channel注册绑定到Worker线程
  • Worker线程组会调配一个EventLoop负责解决该Channel的读写事件,每个EventLoop相当于一个线程。通过Selector进行事件循环监听。
  • 当客户端发动I/O事件时,服务端的EventLoop讲就绪的Channel分发给Pipeline,进行数据的解决
  • 数据传输到ChannelPipeline后,从第一个ChannelInBoundHandler进行解决,依照pipeline链一一进行传递
  • 服务端解决实现后要把数据写回到客户端,这个写回的数据会在ChannelOutboundHandler组成的链中流传,最初达到客户端。

<center>图2-9 Netty各个组件的工作原理</center>

Netty中外围组件的具体介绍

在2.5节中对Netty有了一个全局意识后,咱们再针对这几个组件做一个十分具体的阐明,加深大家的了解。

启动器Bootstrap和ServerBootstrap作为Netty构建客户端和服务端的路口,是编写Netty网络程序的第一步。它能够让咱们把Netty的外围组件像搭积木一样组装在一起。在Netty Server端构建的过程中,咱们须要关注三个重要的步骤

  • 配置线程池
  • Channel初始化
  • Handler处理器构建
版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自 Mic带你学架构
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!