Kong

当机立断,非黑即白,不要后悔

Enjoy programming and build awesome stuff.
Silence makes big money.
Life's most persistent and urgent question is, "What are you doing for others?"


Io与netty原理

什么是IO?

IO是Input、Output的简称,即输入输出。简单说就是读取数据,然后进行系统调用

一、Java IO模型

1、BIO(Blocking IO)

BIO即同步阻塞模型,每个客户端连接对应一个处理线程,在BIO中,accept和read方法都是阻塞操作,没有连接请求时,accept方法阻塞等待,如果无数据读取时,read方法阻塞。

编辑

2、NIO(Non Blocking IO)

        NIO是同步非阻塞模型,与BIO相比引入了多路复用器Selector的概念,服务端一个线程可以处理多个请求,客户端的连接请求注册在一个Selector上,服务端通过轮询Selector查看是否有IO请求。

编辑

NIO三大核心组件:

        Buffer:数据传输的载体,底层基于数组实现,针对java 基本类型提供了对应的缓冲区类。ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,没有 BooleanBuffer。

        Channel:用于数据传输,面向缓冲区进行操作,支持双向传输,数据可以从Channel读取到Buffer中,也可以从Buffer写入到Channel中。

        Selector:多路复用器也叫选择器,当Channel注册近Selector中后,Selector内部通过不断轮询查询Channel是否有已就绪的IO事件,当Channel发生读写事件就会转入就绪状态,selector监听到后通过SelectionKeys可以获取就绪Channel的集合,然后进行后续IO操作。

3、AIO(NIO 2.0)

        AIO是异步非阻塞模型,适用于连接数多且连接时间长的应用,在读写事件完成后由回调服务区通知程序启动线程进行处理。与NIO不同,进行读写操作时,只需直接调用read或write方法,当然这两种方法都是异步的,可以理解为read、write方法都是异步,0完成后主动调用回调函数。

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,获取事件的时候无需遍历整个被监听的描述符集,只需遍历哪些被内核IO事件异步唤醒而加入Ready队列的描述符集。

二、Reactor线程模型

        Reactor模式是基于事件驱动开发的,服务端程序处理传入多路请求并将它们同步分派给对应的请求线程,Reactor模式也叫Dispatcher模式,即IO多路复用统一监听事件,收到事件后分发。

        Reactor以NIO为底层支持,其核心包括Reactor和Handler

                Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应

                Handler:处理程序执行IO事件要完成的实际事件,Reactor通过调度对应的处理程序响应IO事件,Handler执行非阻塞操作。

根据Reactor的数量和Handler线程数量的不同,Reactor可以分为三种模型:        

  • 单线程模型(单Reactor单线程)
  • 多线程模型(单Reactor多线程)
  • 主从多线程模型(多Reactor多线程)

单线程模型

        单线程模型比较简单,Reactor内部通过Selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立的事件则由Acceptor处理,通过accept接受连接,并创建Handler进行处理,如果是读写事件直接调用连接对应的Handler来处理。单线程模型最大的弊端就是它是阻塞的,无法做到高性能,适用于Redis那类快速读写的场景。

多线程模型

        与单线程相比多了一个线程池进行数据处理,单Reactor承担所有事件的监听和响应,Handler只负责响应事件,不进行业务操作(只进行read和write),业务处理交给线程池。

线程池分配一个线程进行业务处理,然后将响应结果交给主进程的Handler,Handler将结果send给client客户端。

主从多线程模型

        多个Reactor,每个Reactor都有自己的Selector选择器,线程和dispatch。主线程中的mainReactor通过自己的Selector监控连接建立事件,收到事件后通过Accpetor接收,将新的连接分配给某个子线程。

        子线程中的subReactor将mainReator分配的连接加入连接队列中通过自己的Selector进行监听,并创建一个Handler处理后续事件。

三、Netty线程模型

Netty线程模型是Reactor模式的一个实现

编辑

1、线程组

Netty中抽象了两组线程池BossGroup和WorkerGroup,都是NIOEventLoopGroup类型,BossGroup用来接收客户端发来的连接,WorkerGroup负责具体的处理。

NIOEventLoopGroup包含多个NioEventLoop,管理NioEventLoop的生命周期。每个NioEventLoop中包含一个Nio selector、一个队列,一个线程;线程用来轮询Selector中的Channel的读写事件和对投递到队列里面的事件进行处理。

BossNioEventLoop线程执行步骤:

->1、处理accept事件,与client建立连接,生成NioSocketChannel

->2、将NioSocketChannel注册到WorkerNioEventLoop上的Selector

->3、处理任务队列的任务

WorkerNioEventLoop线程执行步骤:

->1、轮询注册到自己的Selector上的所有NioSockerChannel的read和write事件

->2、处理read和write事件,在对应的NioSocketChannel处理业务

->3、runAllTasks处理任务队列TaskQueue的任务,一些耗时的任务处理可以放到TaskQUeue中慢慢处理,不影响数据在pipeline中的流动处理

public class NettyServer {
    public static void main(String[] args) {
        /*        * 创建两个NioEventLoopGroup对象,bossGroup用作监听客户端请求,workerGroup处理每条链接的数据读写        * */
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        /*        * 1、ServerBootstrap 作为引导类引导服务器的启动工作        * 2、group配置上面两个NioEventLoopGroup对象的角色        * 3、channel配置服务端的IO模型:NIO模型->NioSocketChannel;        * 4、chilHandler用于配置每条连接的数据读写和业务逻辑        * */
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
            .group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                }
            });
        //绑定监听端口
        serverBootstrap.bind(8000);
    }

2、ChannelPipeline

        Netty中将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有IO事件拦截器(ChannelHandler)的双向链表,由ChannelHandler对IO事件进行拦截和处理,可以方便的新增和删除ChannelHandler来实现不同的业务逻辑定制,实现对修改封闭对扩展开放。

        ChannelPipeline是一系列的ChannelHandler实例,入站和出站事件可以被ChannelPipeline拦截,每一个Channel创建都会绑定一个新的ChannelPipeline。

        一个事件将由ChannelInboundHandler或ChannelOutboundHandler处理,ChannelHandlerContext实现转发或传递给下一个ChannelHandler。ChannelHandler处理程序可以通知下一个ChannelHandler执行。Read(入站事件)和Write(出站事件)使用相同的pipeline,入站事件从链表头往后传递到最后一个入站Handler,出站事件会从链表尾传递到最前一个出站Handler,两种Handler互不干扰。

ChannelInboundHandler回调方法:

  • handlerAdded(): 当业务处理器被加入到流水线后回调
  • channelRegistered(): 当channel成功绑定一个NioEventLoop线程后
  • channelUnregistered():channel和NioEventLoop线程解除绑定
  • channelActive():channel激活时回调
  • channelInactive():channel不活跃时回调
  • channelRead(): channel从远程读取到数据时回调
  • channelReadComplete():read消费完读取的数据后回调
  • channelRemoved():netty移除channel上的所有业务处理器,并回调所有业务处理器的handlerRemoved()

3、异步非阻塞

在使用Netty进行网络通信时,我们发起IO请求后会马上得到返回结果,不会阻塞业务调用线程,即使是获取响应结果也不需要业务调用线程使用阻塞的方式等待,而是通过IO线程异步通知的方式。

        写操作:Netty通过在ChannelPipeline中判断NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程,如果不是则会把写入请求封装为WriteTask投递到期对应的NIOEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询读写事件的时候,从队列中取出并执行。

        读操作:当从NioSocketChannel中读取数据时,NioEventLoop轮询线程发现Selector中有数据就绪,然后事件通知方式通知业务数据已就绪,进行数据读取。

最近的文章

手写rpc框架

手写RPC框架一、内容概览 RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化 Zookeeper注册中心的基本使用 自定义注解实现特殊业务逻辑 Java的动态代理 自定义Spring Boot Starter 二、RPC基础知识2.1 RPCRemote Procedure Call(RPC):远程过程调用。借助网络通信实现想通用...…

继续阅读
更早的文章

Redis(一)五种基本类型

一、什么是NoSQL?NoSql实际是NOT ONLY SQL,不仅SQL的意思。关系型数据库是行+列的数据结构,同一表下数据结构是一样的,而非关系型数据库没有固定的格式,并且可以进行横向扩展。NoSQL泛指非关系型数据库,传统的关系型数据库难以应对大数据尤其是超大规模高并发的处理,NoSQL在当今大数据环境下发展迅速,主流的NoSQL数据库有 Cassandra、 Mongodb、CouchDB、Redis、 Riak、 Membase、Neo4j、HBase等。传统RDBMS ...…

继续阅读