11 | 答疑课堂:深入了解NIO的优化实现原理
11 | 答疑课堂:深入了解NIO的优化实现原理
讲述:李良
时长15:35大小14.25M
网络 I/O 模型优化
1. 阻塞式 I/O
2. 非阻塞式 I/O
3. I/O 复用
4. 信号驱动式 I/O
5. 异步 I/O
零拷贝
线程模型优化
1. 单线程 Reactor 线程模型
2. 多线程 Reactor 线程模型
3. 主从 Reactor 线程模型
基于线程模型的 Tomcat 参数调优
赞 15
提建议
精选留言(42)
- -W.LI-2019-06-13老师好!万分感觉,写的非常非常好谢谢。不过开心的同时,好多没看懂:-(先讲下我的理解吧。 阻塞IO:调用read()线程阻塞了 非阻塞IO:调用read()马上拿到一个数据未就绪,或者就绪。 I/O多路复用:selector线程阻塞,channel非阻塞,用阻塞一个selector线程换了多个channel了非阻塞。select()函数基于数组,fd个数限制1024,poll()函数也是基于数组但是fd数目无限制。都会负责所有的fd(未就绪的开销浪了), epll()基于红黑数实现,fd无大小限制,平衡二叉数插入删除效率高。 信号驱动模式IO:对IO多路复用进一步优化,selector也非阻塞了。但是sign信号无法区分多信号源。所以socket未使用这种,只有在单一信号模型上才能应用。 异步IO模型:真正的非阻塞IO,其实前面的四种IO都不是真正的非阻塞IO,他们的非阻塞只是,从网络或者内存磁盘到内核空间的非阻塞,调用read()后还需要从内核拷贝到用户空间。异步IO基于回调,这一步也非阻塞了,从内核拷贝到用户空间后才通知用户进程。 能我是这么理解的前半断,有理解错的请老师指正谢谢。后半断没看完。展开
作者回复: 理解正确,赞一个
26 - QQ怪2019-06-13老师这篇可以配合隔壁专栏tomcat的13,14章一起看,会更加有味道。😃共 1 条评论21
- -W.LI-2019-06-14老师好对Reacktor的三种模式还是理解不太好。帮忙看看哪里有问题 单线程模型:一个selector同时监听accept,事件和read事件。检测到就在一个线程处理。 多线程模型:一个线程监听accept事件,创建channel注册到selector上,检听到Read等事件从线程池中获取线程处理。 主从模式:没看懂:-(,一个端口只能被一个serverSocketChannel监听,第二个好像会报错?这边的主从怎么理解啊展开
作者回复: 主从模式则是,Reactor主线程主要处理监听连接事件,而Reactor从线程主要监听I/O事件。这里是多线程处理accept事件,而不是创建多个ServerSocketChannel。
共 3 条评论15 - 每天晒白牙2019-06-15老师您在介绍Reactor线程模型的时候,关于多线程Reactor线程模型和主从Reactor线程模型,我有不同的理解。您画的多线程模型,其中读写交给了线程池,我在看Doug Lea的 《Scalable in java》中画的图和代码示例,读写事件还是由Reactor线程处理,只把业务处理交给了线程池。主从模型也是同样的,Reactor主线程处理连接,Reactor从线程池处理读写事件,业务交给单独的线程池处理。 还望老师指点展开
作者回复: 你好,Reactor是一个模型,每个框架或者每个开发人员在处理I/O事件可能不一样,根据自己业务场景来处理。 Netty是基于Reactor主线程去监听连接, Reactor从线程池监听读写事件,同时如果监听到事件后直接在该从线程中操作读写I/O,将业务交给单独的业务线程池,也可以不交给单独的线程池处理,直接在从线程池处理。不交给业务线程池的好处是,减少上下文切换,坏处是会造成线程阻塞。 所以根据自己的业务的特性,如果你的数据特别大,I/O读写操作放到handler线程池,,Reactor从线程数量有限,如果开大了,由于开多个多路复用器也会带来性能消耗。所以这种处理也是一种提高系统吞吐量的优化。
14 - 你好旅行者2019-06-17I/O多路复用其实就相当于用了一个专门的线程来监听多个注册的事件,而之前的IO模型中,每一个事件都需要一个线程来监听,不知道我这样理解的是否正确?老师我还有一个问题,就是当select监听到一个事件到来时,它是另起一个线程把数据从内核态拷贝到用户态,还是自己就把这个事儿给干了?
作者回复: 理解正确。select监听到事件之后就用当前线程把数据从内核态拷贝到用户态。
共 2 条评论8 - 行者2019-07-14感谢老师分享,联想到Redis的单线程模式,Redis使用同一个线程来做selector,以及处理handler,这样的优点是减少上下文切换,不需要考虑并发问题;但是缺点也很明显,在IO数据量大的情况下,会导致QPS下降;这是由Redis选择IO模型决定的。
作者回复: 对的,redis本身是操作内存,所以读取数据的效率会高很多。
6 - z.l2019-06-16老师,隔壁李号双老师的《深入拆解Tomcat & Jetty》中关于DirectByteBuffer的解释和您不一样,他的文章中DirectByteBuffer的作用是:DirectByteBuffer 避免了 JVM 堆与本地内存直接的拷贝,而并没有避免内存从内核空间到用户空间的拷贝。而sendfile 特性才是避免了内核与应用之间的内存拷贝。请问哪种才是对的?
作者回复: 这里的本地内存应该指的是物理内存,避免堆内存和物理内存的拷贝,其实就是避免内核空间和用户空间的拷贝。
共 5 条评论6 - 余冲2019-06-18老师能对reactor的几种模型,给一个简单版的代码例子看看吗。感觉通过代码应该能更好的理解理论。
作者回复: 好的,后面补上
共 2 条评论4 - 阿卧2020-02-25老师,redis的io多路复用模型,用的是单线程reactor线程模型嘛?
作者回复: 对的,redis在处理文件事件(例如GET SET命令)时是通过事件处理器循环顺序处理各个事件。
3 - chp2020-04-21老师,为什么说NIO是同步非阻塞呀?同步我知道原因,那个非阻塞搞不懂,select函数不是已经阻塞了吗,这块要怎么理解呢
作者回复: 这里说的非阻塞,是伪非阻塞,操作系统层面的epoll还是阻塞的,正在实现操作系统层面的非阻塞是AIO
2 - insist2020-01-04感谢老师的讲解,很细致,从底层原理解释了5中IO模型。在netty,或者其他课程中,都有接触到这类知识,但是一直没有总结,总是看了感觉自己知道了,但是过段时间遇到这类问题,又不知道是为什么。
作者回复: 大家都一样,有时间偶尔捡起来再看看,温故而知新,可以为师矣
2 - 陆离2019-06-13我所使用的Tomcat版本是9,默认的就是NIO,是不是版本不同默认模型也不同? directbuffer如果满了会阻塞还是会报错?这一块的大小设置是不是也可以优化? 因为Linux的aio这一块不成熟所以nio现在是主流?还是有其他原因?
作者回复: 是的,在Tomcat9版本改成了默认NIO。 在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势; 这个跟堆内存溢出是类似的道理,如果物理内存被分配完了就会出现溢出错误。NIO中的directbuffer是用来分配内存读取或写入数据操作,如果数据比较大,而directbuffer分配比较下,则会分多次去读写,如果数据比较大的情况下可以适当调大提高效率。
2 - null2021-04-20第 8 课原文: DirectBuffer 只优化了用户空间内部的拷贝,而之前我们是说优化用户空间和内核空间的拷贝,那 Java 的 NIO 中是否能做到减少用户空间和内核空间的拷贝优化呢? 第 11 课原文: 在 Java 的 NIO 编程中,则是使用到了 Direct Buffer 来实现内存的零拷贝。Java 直接在 JVM 内存空间之外开辟了一个物理内存空间,这样内核和用户进程都能共享一份缓存数据。 老师,您好! 第 8 课说:DirectBuffer 只优化了用户空间内部的拷贝,并非优化用户空间和内核空间的。第 8 课提到的是 MappedByteBuffer 调用 mmap 文件内存映射,直接从硬盘读取数据到用户空间,没有经过内核空间。 第 11 课对 DirectBuffer 描述为:内核和用户进程都能共享一份缓存数据。 问题: 第 8 课讲只能优化用户空间。第 11 课讲优化内核空间和用户空间。这 2 节课的描述是否有出入吖? 谢谢老师!展开1
- AA2020-01-09acceptCount是Acceptor主线程数?
作者回复: 是的
1 - kaixiao72019-07-11老师,有两个疑惑还望您解答,谢谢 1. ulimit -n 显示单个进程的文件句柄数为1024,但是启动一个socket服务(bio实现)后, cat /proc/<pid>/limits 中显示的open files为4096, 实际测试当socket连接数达到4000左右时就无法再连接了. 还请老师解答一下4096怎么来的, 为什么1024不生效呢? 2. 您在文中提到epoll不受fd的限制. 但是我用NIO实现的服务端也是在连接到4000左右时无法再接收新的连接, 环境为Centos7(虚拟机, 内核3.10, 除了系统之外, 没有跑其他程序), jdk1.8, ulimit -n 结果为1024, cat /proc/sys/fs/file-max 结果为382293. 按理说socket连接数最大可以达到38000左右, 代码如下: public static void main(String[] args) throws IOException { ServerSocketChannel channel = ServerSocketChannel.open(); Selector selector = Selector.open(); channel.configureBlocking(false); channel.socket().bind(new InetSocketAddress(10301)); channel.register(selector, SelectionKey.OP_ACCEPT); int size = 0; while (true) { if (selector.select() == 0) { continue; } Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isAcceptable()) { size++; ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); System.out.println("当前客户端连接数: " + size + ", " + client.getRemoteAddress()); } } } }展开共 2 条评论1
- accpan2022-08-19 来自上海总结的很到位
- 平民人之助2021-07-04其实大部分的公司都用undertow了,这个是默认开启nio吗
- Mars2021-07-03Tomcat 中,BIO、NIO 是基于主从 Reactor 线程模型实现的。这句话不是很清楚,Reactor 的核心思想是将 I/O 事件注册到多路复用器上,一旦有 I/O 事件触发,多路复用器就会将事件分发到事件处理器中,执行就绪的 I/O 事件操作。那BIO没有所有的I/O事件注册,那怎么套用Reator模型呢?
- Geek_f67f402021-04-23不知道老师用的是哪个版本,似乎没有mmap的源码显示,或者说,epoll底层根本没使用mmap?共 1 条评论
- warriorSL2021-03-07这篇文章对于理解服务器处理网络请求线程模型真是太优秀了