Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

11 | 答疑课堂:深入了解NIO的优化实现原理

刘超 2019-06-13
你好,我是刘超。专栏上线已经有 20 多天的时间了,首先要感谢各位同学的积极留言,交流的过程使我也收获良好。
综合查看完近期的留言以后,我的第一篇答疑课堂就顺势诞生了。我将继续讲解 I/O 优化,对大家在 08 讲中提到的内容做重点补充,并延伸一些有关 I/O 的知识点,更多结合实际场景进行分享。话不多说,我们马上切入正题。
Tomcat 中经常被提到的一个调优就是修改线程的 I/O 模型。Tomcat 8.5 版本之前,默认情况下使用的是 BIO 线程模型,如果在高负载、高并发的场景下,可以通过设置 NIO 线程模型,来提高系统的网络通信性能。
我们可以通过一个性能对比测试来看看在高负载或高并发的情况下,BIO 和 NIO 通信性能(这里用页面请求模拟多 I/O 读写操作的请求):
测试结果:Tomcat 在 I/O 读写操作比较多的情况下,使用 NIO 线程模型有明显的优势。
Tomcat 中看似一个简单的配置,其中却包含了大量的优化升级知识点。下面我们就从底层的网络 I/O 模型优化出发,再到内存拷贝优化和线程模型优化,深入分析下 Tomcat、Netty 等通信框架是如何通过优化 I/O 来提高系统性能的。

网络 I/O 模型优化

网络通信中,最底层的就是内核中的网络 I/O 模型了。随着技术的发展,操作系统内核的网络模型衍生出了五种 I/O 模型,《UNIX 网络编程》一书将这五种 I/O 模型分为阻塞式 I/O、非阻塞式 I/O、I/O 复用、信号驱动式 I/O 和异步 I/O。每一种 I/O 模型的出现,都是基于前一种 I/O 模型的优化升级。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(20)

  • QQ怪
    老师这篇可以配合隔壁专栏tomcat的13,14章一起看,会更加有味道。😃
    2019-06-13
    1
    8
  • -W.LI-
    老师好!万分感觉,写的非常非常好谢谢。不过开心的同时,好多没看懂:-(先讲下我的理解吧。
    阻塞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基于回调,这一步也非阻塞了,从内核拷贝到用户空间后才通知用户进程。
    能我是这么理解的前半断,有理解错的请老师指正谢谢。后半断没看完。

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

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

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

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

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

    2019-06-16
    2
    3
  • 每天晒白牙
    老师您在介绍Reactor线程模型的时候,关于多线程Reactor线程模型和主从Reactor线程模型,我有不同的理解。您画的多线程模型,其中读写交给了线程池,我在看Doug Lea的 《Scalable in java》中画的图和代码示例,读写事件还是由Reactor线程处理,只把业务处理交给了线程池。主从模型也是同样的,Reactor主线程处理连接,Reactor从线程池处理读写事件,业务交给单独的线程池处理。
    还望老师指点

    作者回复: 你好,Reactor是一个模型,每个框架或者每个开发人员在处理I/O事件可能不一样,根据自己业务场景来处理。

    Netty是基于Reactor主线程去监听连接, Reactor从线程池监听读写事件,同时如果监听到事件后直接在该从线程中操作读写I/O,将业务交给单独的业务线程池,也可以不交给单独的线程池处理,直接在从线程池处理。不交给业务线程池的好处是,减少上下文切换,坏处是会造成线程阻塞。

    所以根据自己的业务的特性,如果你的数据特别大,I/O读写操作放到handler线程池,,Reactor从线程数量有限,如果开大了,由于开多个多路复用器也会带来性能消耗。所以这种处理也是一种提高系统吞吐量的优化。

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

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

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

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

    2019-06-18
    1
  • 邱柏森
    > poll() 函数:在每次调用 select() 函数之前,系统需要把一个 fd 从用户态拷贝到内核态,这样就给系统带来了一定的性能开销
    这里应该是拷贝三组fd_set
    2019-12-03
  • yunfeng
    2019.10.15 需要细看
    2019-10-15
  • 烈冬冰夏
    Tomcat 中,BIO、NIO 是基于主从 Reactor主从,那2则的区别是什么呢

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

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

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

    2019-06-18
  • 趙衍
    I/O多路复用其实就相当于用了一个专门的线程来监听多个注册的事件,而之前的IO模型中,每一个事件都需要一个线程来监听,不知道我这样理解的是否正确?老师我还有一个问题,就是当select监听到一个事件到来时,它是另起一个线程把数据从内核态拷贝到用户态,还是自己就把这个事儿给干了?

    作者回复: 理解正确。select监听到事件之后就用当前线程把数据从内核态拷贝到用户态。

    2019-06-17
  • -W.LI-
    老师好!又看了一遍总结了下
    epoll()方式的优点如下
    1.无需用户空间到内核空间的fd拷贝过程。
    2.通过事件表,只返回就绪事件无需轮训遍历
    3.基于红黑树增删快。
    4.事件发生后内核主动回调,用户进程wait状态(此时算阻塞还是非阻塞啊?)
    内核也像观察者,(事件驱动的都像观察者)
    还有别的优点么?

    作者回复: epoll是使用了wait方法阻塞等待事件,所以是阻塞的。

    2019-06-15
  • 西兹兹
    刘老师,请问poller线程池 poller队列和文末提的acceptCount队列是不是一个队列?

    作者回复: 不是的,这个acceptCount是Acceptor的线程数量,也就是Reactor主线程数量。

    2019-06-14
  • Demon.Lee
    晕了,如果能结合点生活中的例子就更好了,我先去看看其他资料,再回来提问题。
    2019-06-13
  • kim118000
    acceptorThreadCount:该参数代表 Acceptor 的线程数量,在请求客户端的数据量非常巨大的情况下,可以适当地调大该线程数量来提高处理请求连接的能力,默认值为 1。

    老师,我今天看了源码注释
    doesn't seem to work that well with mutiple acceptor threads

    我之前的理解是Java还做不到多个接受连接来提高请求连接的处理能力,目前普遍的做法是通过fork多个子进程来达到同时监听同一个socket fd,但这样有惊群,所以利用mutex多个监听者只有一个能处理本次连接操作

    目前还是一主多从方案,但这已经够用,可以通过多台机器提高并发。
    2019-06-13
  • 陆离
    我所使用的Tomcat版本是9,默认的就是NIO,是不是版本不同默认模型也不同?
    directbuffer如果满了会阻塞还是会报错?这一块的大小设置是不是也可以优化?
    因为Linux的aio这一块不成熟所以nio现在是主流?还是有其他原因?

    作者回复: 是的,在Tomcat9版本改成了默认NIO。

    在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势;

    这个跟堆内存溢出是类似的道理,如果物理内存被分配完了就会出现溢出错误。NIO中的directbuffer是用来分配内存读取或写入数据操作,如果数据比较大,而directbuffer分配比较下,则会分多次去读写,如果数据比较大的情况下可以适当调大提高效率。

    2019-06-13
收起评论
20
返回
顶部