Netty(一):源码解析之三种IO支持

文章探讨了三种经典的IO模型(BIO,NIO,AIO),重点讲解了Netty为何仅支持NIO以及NIO的Reactor模式。Netty的Reactor实现包括单线程、主从模式以及结合线程池的版本,以解决并发处理和资源效率问题。作者强调,Reactor模式通过非阻塞IO和事件驱动提高系统性能。
摘要由CSDN通过智能技术生成

【关于作者】

关于作者,目前在蚂蚁金服搬砖任职,在支付宝营销投放领域工作了多年,目前在专注于内存数据库相关的应用学习,如果你有任何技术交流或大厂内推及面试咨询,都可以从我的个人博客(https://0522-isniceday.top/)联系我

1.三种经典IO

  • 阻塞同步IO(BIO)
  • 非阻塞同步IO(NIO)
  • 非阻塞异步IO(AIO)

详细可看:https://0522-isniceday.top/archives/ji-suan-ji-wang-luo–i-o-mo-xing#more

2.Netty对三种IO的支持

相关api如下:

image-20210920161809448

3.Netty为什么仅仅支持NIO

  • 为什么不建议(deprecate)阻塞 I/O(BIO/OIO)?

    连接数高的情况下:阻塞 -> 耗资源、效率低

  • 为什么删掉已经做好的 AIO 支持?

    • Windows 实现成熟,但是很少用来做服务器
    • Linux 常用来做服务器,但是 AIO 实现不够成熟
    • Linux 下 AIO 相比较 NIO 的性能提升不明显

4.三种IO分别采取的开发模式

image-20210921153548798

其中NIO采取的就是我们下文所要详细讲述的Reactor模式

Thread-Per-Connection相当于就是为每一个连接分配一个线程去处理,但是这里会受到C10K的约束

代码如下:

package com.linwuee.netty.reactor;

import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhang_yx
 * @Date: 2021/9/21 15:04
 */
public class ThreadPerConnection {

    public static void main(String[] args) {
        new Thread(new Server()).start();
    }

}


class Server implements Runnable{

    @Override
    public void run() {
        try {

            ServerSocket serverSocket = new ServerSocket(8080);
            while (!Thread.interrupted()){
                //这里会阻塞
                final Socket socket = serverSocket.accept();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getId());
                        //TODO 获得已连接套接字,去处理请求
                    }
                }).start();
            }
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

5.NIO的三种Reactor模型的实现

5.1.Reactor是什么

Reactor可以看做一种设计模式,它要求主线程(IO处理单元)只负责监听文件描述符(fd)是否有事件发生,有的话就立即将该事件通知工作线程(handler)。主线程不做除了监听事件之外的任何操作,读写数据,客户端的请求等均在工作线程中完成。其中主线程往往是阻塞在某一个调用上,并监听。

其中Reactor模式,是处理并发IO比较常见的模式,使用同步非阻塞IO实现。其会将要处理的IO事件放到一个IO多路复用器中,同时主线程阻塞在该多路复用器(select、poll、epoll)上,直到有IO事件准备就绪了,多路解复用器从阻塞处返回,将准备好的IO事件分发到事件处理器(工作线程handler)中

Reactor是基于非阻塞同步IO+事件驱动技术(或+线程池)的一种并发模型

Reactor模式通过poll、epoll等IO分发技术实现的一个无限循环的事件分发程序。获取到一个已连接事件之后,会触发添加监听更多的读写事件

其中可总结为:注册感兴趣的事件 -> 扫描是否有感兴趣的事件发生 -> 事件发生后做出相应的处理

其中事件如下,其中

  • 客户端的套接字:连接、读、写事件
  • 服务端的监听套接字:获得连接事件
  • 服务端的已连接套接字:读、写事件

image-20210921194308472

5.2.Reactor模型详细设计

image-20201209004038532

  • Handle:描述符,或者文件句柄,代表操作的资源,例如socketfd。其中会注册到Synchronous Event Demultiplexer(同步事件多路解复用器)中,进行监听Handle上发生的事件,如:Connect,Read,Write,Close等事件(一个handle会有多个事件);
  • Synchronous Event Demultiplexer(同步事件多路解复用器):该组件阻塞等待被监听的事件集,等待事件发生,一般使用IO复用技术实现,如select、poll、epoll;
  • Reactor:类似于程序门面,持有Event Handler,包含注册、删除Event Handler,其中也持有了Synchronous Event Demultiplexer去执行事件的监听,然后根据发生事件的文件描述符和事件类型交给不同的Event Handler去处理
  • Event Handler:事件处理器,处理具体的IO事件,对IO事件作出具体的响应

其中通过类图也大概能够看出流程很简单,大概如下:

  1. 注册Event Handler(如果是网络IO框架,这一步对应创建监听套接字,并且把监听套接字事件处理器注册到Reactor中),其实相当于创建Acceptor,单线程下的Reactor,多路复用器承担了Acceptor的职责
  2. Reactor调用多路复用器中的监听方法,阻塞获取handles(例如已连接套接字),此时可将handle关联Event Handler并注册入Reactor中,方便该handle产生事件时能够找到handler执行(所以也说handler是回调方法)
  3. 获得监听事件并将事件交给对应的Event Handler去执行(每个Handle都关联了一个Handler)

现在针对上述流程,也会产生一个问题,就是说当handler执行事件的处理逻辑时,会阻塞多路复用器中的监听方法,因此此时可将事件的处理丢入线程池中取异步执行。

5.3.Reactor模型的三种实现

上面我们说了,事件的再handler中的处理流程可能会阻塞多路复用器的调用,因此可将事件处理丢入线程池去异步执行

5.3.1.单线程版本的Reactor

上述我们所讲的Reactor流程就是单线程版本的实现,其中多路复用器承担了Acceptor的职责,大概流程如下:

image-20201218000043160

  • Accept接收新连接,把新连接IO读写事件注册到同步事件多路解复用器
  • 执行dispatch调用多路解复用器阻塞等待IO事件;
  • 分发事件到特定的Handler中处理,测试会阻塞dispatch流程;

当描述符可读的时候,才会去执行read,想要往socket写数据的时候,统一先写到输出缓冲区,等到感知到可写事件的时候,再统一把输出缓冲区的数据写到网卡即可,从而避免了读和写的阻塞。

我们需要明白,多路复用器本质就是将往内核的多次IO操作,通过channel一次完成

单线程版本中的Handler处理会阻塞Reactor的Event Loop线程的IO轮训,为此,我们可以把Handler丢到单独的线程池中进行处理

5.3.2.主从版本的Reactor

单线程版本存在的问题主要是Reactor可能因为Event Handler处理速度太占用CPU时间,并且会阻塞dispatch流程,因此可能会影响连接套接字不能及时通过Reactor注册到Synchronous Event Demultiplexer中进行IO轮训

因此我们可以将监听套接字(handle)与已连接套接字(handle)交给不同的Reactor模型去监听,因此就有了主从模式

主Reactor线程只负责执行dispatch阻塞等待监听套接字的连接事件,当有套接字连接之后,随机选择一个Sub-Reactor,把已连接套接字的IO读写事件注册到选择的sub-Reactor线程中

Sub-Reactor线程负责阻塞等待已连接套接字的IO事件的到来,并且调用IO事件的Event Handler处理IO读写事件。这样就实现了IO的高效分发

image-20201217230206165

但是,不管主从Reactor,其中内部对于事件的处理仍然会阻塞dispatch的执行,因此我们还有单线程+主从模式结合线程池的实现

5.3.3.单线程的Reactor+线程池(多线程)

image-20201218000146145

在read到了数据之后,compute这些操作都是业务需要去处理的,因此可将这些内容交给线程池去具体执行,从而加快了时间处理速度

5.3.4.主从的Reactor+线程池(多线程)

同理

image-20201218000305993

5.Netty关于Reactor的实现

image-20210921204201212

我们始终需要明白,单线程,指的是针对socketfd具体事件的处理是单线程的,存在阻塞dispatch的情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈哈哈张大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值