• QQ怪
    2019-06-13
    老师这篇可以配合隔壁专栏tomcat的13,14章一起看,会更加有味道。😃
     1
     9
  • -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基于回调,这一步也非阻塞了,从内核拷贝到用户空间后才通知用户进程。
    能我是这么理解的前半断,有理解错的请老师指正谢谢。后半断没看完。
    展开

    作者回复: 理解正确,赞一个

    
     8
  • -W.LI-
    2019-06-14
    老师好对Reacktor的三种模式还是理解不太好。帮忙看看哪里有问题
    单线程模型:一个selector同时监听accept,事件和read事件。检测到就在一个线程处理。
    多线程模型:一个线程监听accept事件,创建channel注册到selector上,检听到Read等事件从线程池中获取线程处理。
    主从模式:没看懂:-(,一个端口只能被一个serverSocketChannel监听,第二个好像会报错?这边的主从怎么理解啊

    作者回复: 主从模式则是,Reactor主线程主要处理监听连接事件,而Reactor从线程主要监听I/O事件。这里是多线程处理accept事件,而不是创建多个ServerSocketChannel。

     1
     6
  • z.l
    2019-06-16
    老师,隔壁李号双老师的《深入拆解Tomcat & Jetty》中关于DirectByteBuffer的解释和您不一样,他的文章中DirectByteBuffer的作用是:DirectByteBuffer 避免了 JVM 堆与本地内存直接的拷贝,而并没有避免内存从内核空间到用户空间的拷贝。而sendfile 特性才是避免了内核与应用之间的内存拷贝。请问哪种才是对的?

    作者回复: 这里的本地内存应该指的是物理内存,避免堆内存和物理内存的拷贝,其实就是避免内核空间和用户空间的拷贝。

     2
     3
  • 每天晒白牙
    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从线程数量有限,如果开大了,由于开多个多路复用器也会带来性能消耗。所以这种处理也是一种提高系统吞吐量的优化。

    
     3
  • 行者
    2019-07-14
    感谢老师分享,联想到Redis的单线程模式,Redis使用同一个线程来做selector,以及处理handler,这样的优点是减少上下文切换,不需要考虑并发问题;但是缺点也很明显,在IO数据量大的情况下,会导致QPS下降;这是由Redis选择IO模型决定的。

    作者回复: 对的,redis本身是操作内存,所以读取数据的效率会高很多。

    
     2
  • 余冲
    2019-06-18
    老师能对reactor的几种模型,给一个简单版的代码例子看看吗。感觉通过代码应该能更好的理解理论。

    作者回复: 好的,后面补上

    
     1
  • 没事走两步
    2020-01-09
    acceptCount是Acceptor主线程数?

    作者回复: 是的

    
    
  • insist
    2020-01-04
    感谢老师的讲解,很细致,从底层原理解释了5中IO模型。在netty,或者其他课程中,都有接触到这类知识,但是一直没有总结,总是看了感觉自己知道了,但是过段时间遇到这类问题,又不知道是为什么。

    作者回复: 大家都一样,有时间偶尔捡起来再看看,温故而知新,可以为师矣

    
    
  • 阿杜
    2019-12-26
    零拷贝,每次io都要经历四次数据拷贝,零拷贝是Direct Buffer实现,java直接申请了一块物理内存,数据直接从内核给用户进程。
    
    
  • 穿越亚平宁的盛夏
    2019-12-25
    二刷,还是刘老师写的好,看的舒服
    
    
  • 村夫
    2019-12-24
    学到很多
    
    
  • 阿杜
    2019-12-17
    在 NIO 服务端通信编程中,首先会创建一个 Channel,用于监听客户端连接;接着,创建多路复用器 Selector,并将 Channel 注册到 Selector,程序会通过 Selector 来轮询注册在其上的 Channel,当发现一个或多个 Channel 处于就绪状态时,返回就绪的监听事件,最后程序匹配到监听事件,进行相关的 I/O 操作。
    这段总结的很好。
    还有了解到线程池的逻辑还是有很多地方实现的,像这里的reactor也是,而且参数基本和java线程池参数一致的。看来很多优秀的实现逻辑在不同的应用中是可以复制的。
    展开
    
    
  • 邱柏森
    2019-12-03
    > poll() 函数:在每次调用 select() 函数之前,系统需要把一个 fd 从用户态拷贝到内核态,这样就给系统带来了一定的性能开销
    这里应该是拷贝三组fd_set
    
    
  • yunfeng
    2019-10-15
    2019.10.15 需要细看
    
    
  • 烈冬冰夏
    2019-09-19
    Tomcat 中,BIO、NIO 是基于主从 Reactor主从,那2则的区别是什么呢

    作者回复: 两者区别在于BIO是阻塞IO,而NIO为非阻塞IO

    
    
  • godtrue
    2019-09-09
    可能功力未到,这次关于各种IO的通信模型没完全GET到点,这一部分又是必须要弄明白的,今年双十一又要开始一波囤书,《UNIX 网络编程》已加入购物车。
    
    
  • kaixiao7
    2019-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());
                    }
                }
            }
            
        }
    展开
     1
    
  • Geek_ebda96
    2019-06-25
    老师,请教一个问题,maxthreads这个参数在tomcat中是指只是单独处理I/o的读写线程数,还是读取完数据后,本身的业务层处理也是在这个线程池里处理
    
    
  • 吾爱有三
    2019-06-18
    文章多次提到挂起会进入阻塞状态,然到挂起等价阻塞?不是吧

    作者回复: 这里的挂起是一个动作,阻塞是一种状态。

    
    
我们在线,来聊聊吧