漫谈NIO

1、传统socket的痛点

1.1 最基本的网络通信程序

单线程用下面的一个图来说明,它最大的特点是一个一个轮流来处理请求,如果一个请求被block住,那后面的请求只有等待的份了,马上有人就提出了优化方案,现在只有一个线程来处理,如果用多线程,是不是马上就解决了这个问题呢?多线程解决了此场景下的吞吐量的问题。

dd8a35d11b29128282c9a99e4de1658d.png
a18a8b4b9eef7a49d22833f51c6e271a.png


1.2 多线程模式

上面提到多线程模式是可以提高吞吐量的,用下面的图来示例说明。写了一个简单的例子来加以说明,现在假设线程池大小设置为2,如果一个线程处理一个请求耗时比较长(15s),则该线程一直被占有,第三个连接进来是处理不了的。


370e6c2ac0516e88622730bf1fdda70d.png
5b8f22c4c097b39e68d92c50f3d22633.png


while (true) {
Socket socket = server.accept();
System.out.println("获取到一个链接进来");

executor.execute(new MulThread(socket));
}

输出结果为:

获取到一个链接进来
Sat Mar 17 14:00:01 CST 2018 ====> client:I am SocketClient1
获取到一个链接进来
Sat Mar 17 14:00:03 CST 2018 ====> client:I am SocketClient2
获取到一个链接进来
Sat Mar 17 14:00:16 CST 2018 ====> client:I am SocketClient3

第三个连接与第一个连接间隔15s。

再深入分析多线程模式的弊端:

  • 线程数量设置多少是一个头痛的问题,如果设置非常大,线程开销太大了,并且还有线程上下文切换。
  • 不管上面的单线程模式还是多线程模式,本质上读写是阻塞的,JDK原文是这样说的:

This method blocks until input data is available, end of file is detected, or an exception is thrown.

  • 读写模式切换的问题,读是先从内核态拷贝到用户态的缓冲区中,这个也是耗时点。

到了这里,有必要总结一下了:

1) 传统的读写模式是阻塞模式,直到有数据才会返回,并且此时完全占用线程,导致线程池被用完,后面的请求没有线程来处理了;
2)优化的本质是找到瓶颈,优化瓶颈,可以发现有三点可以优化:

  • a. 阻塞变非阻塞,有数据才处理,否则别"占着茅坑不拉shi"啊;
  • b. 读写要从内核空间到用户空间进行切换,能不能进行共享呢;
  • c. 现在的读写是分别从socket里获取输入、输出流(单向模式),能不能有双向的呢(减少一倍的开销)。

通过上面的分析,一种新的IO模式(NIO)就呼之欲出了,它的三把斧(select、channel、buffer)就是对应上面三点优化。

2、NIO (Non-block IO)

从上面一路读下来,基本上能明白NIO产生的背景以及要解决的问题。它的目标很明确:解决传递IO的痛点。下面继续分析,NIO是怎样解决这些痛点的呢。

2.1 select

在说明select之前,有必要说明一下几个概念。

  • 同步 vs 异步

其实同步和异步是通信里的概念,主要是说是主动等待或者轮询询问结果,还是被动通知结果。

  • 阻塞 vs 非阻塞

表现在程序遇到读写时,能否向下执行。

select解决的是"阻塞变非阻塞,有数据才处理",这个要怎么实现呢?有就处理这种模式最经典的出现在订阅者模式或者在事件模式中出现,select也是用这个模式来实现的,它会根据事件(连接、可读、可写)来进行处理,IO的处理速度与CPU相比,不是在同一个数量级上,这也是为什么用select之后,处理多连接变得很轻松。

介绍select的原理文章一大把,在网上一搜有很多介绍。在这里用另外一种表述来介绍它。上面提到它是用事件模式来实现的,事件模式本质上是异步的,异步的本质是数据暂存+定时处理,这是从最高层抽象得出的结论,不妨去看看MQ等异步实现原理,是不是这个道理。所以有一个队列来存储请求的,内核不断扫描这些请求,现在它的状态是什么(是否是可读,可写),满足哪一项,就进行唤醒处理。

2.2 buffer

buffer是为了解决"读写要从内核空间到用户空间进行切换",内存映射(mmp)相信大家不陌生,内核与用户态是共享同一个数据地址空间的,不会进行模式切换的。现有操作系统是支持这个功能的,一种是定时刷数据到磁盘上,另一种是手动调用force强制刷到磁盘上。具体buffer有一些函数操作,这里不列举了。

2.3 channel

channel 必须要与buffer结合来使用的,好比一个是火车,一个是火车的列车厢,channel是具有双向的,这个与传统的流操作不一样,流是单向的。channel有FileChannel、SocketChannel,具体的用法可以参考相应的资料。

关于NIO的例子就不贴了,网上很多,把原理性的东西搞清楚了,基本上就知道它内部的原理、结构,剩下的就是实践操作了,也有一些NIO的框架,如netty,mina都是NIO的框架。

3、总结

从传统IO的痛点切入,分析传统IO存在的问题点,并对这些问题点进行分析,一步一步分析得出优化策略,最后得出NIO的三把斧(select、channel、buffer),以及各自的原理特点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值