为什么Netty使用NIO而不是AIO?

先了解下BIO NIO AIO

BIO:同步并阻塞,一个连接一个线程,适用于链接数量小且固定的架构。
NIO:同步非阻塞:一个请求一个线程,客户端发送的链接请求都会注册到多路复用器
上,多路复用器轮训到链接有 io 请求时才启动一个线程进行处理,适用于链接比较多,比
较短。以块的方式处理数据。采用多路复用Reactor模式。JDK1.4时引入
AIO:异步非阻塞,一个有效请求一个线程,适用于链接数目多且长。基于unix事件驱动,不需要多路复用器对注册通道进行轮询,采用Proactor设计模式。JDK1.7时引入。

为什么Netty使用NIO而不是AIO?

  1. Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化
  2. Netty整体架构是reactor模型, 而AIO是proactor模型, 混合在一起会非常混乱,把AIO也改造成reactor模型看起来是把epoll绕个弯又绕回来
  3. AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存, 所以对连接数量非常大但流量小的情况, 内存浪费很多
  4. Linux上AIO不够成熟,处理回调结果速度跟不到处理需求,比如外卖员太少,顾客太多,供不应求,造成处理速度有瓶颈(待验证)

一个比喻:

NIO相当于餐做好了自己去取,AIO相当于送餐上门。要吃饭的人是百万,千万级的,送餐员也就几百人。所以一般要吃到饭,是自己去取快呢,还是等着送的更快?目前的外卖流程不是很完善,所以时间上没想的那么靠谱,但是有优化空间,这就是AIO。

作者原话:

Not faster than NIO (epoll) on unix systems (which is true)
There is no daragram suppport
Unnecessary threading model (too much abstraction without usage)

Reactor VS Proactor

主动和被动

以主动写为例:

  • Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据;写完处理后续逻辑;
  • Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑;
    可以看出,Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作;

  • Proactor直接调用异步读写操作,调用完后立刻返回;

实现

  • Reactor实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应;

  • Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)

优点

  • Reactor实现相对简单,对于耗时短的处理场景处理高效;

  • 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性;

  • 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁;

  • 事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,

  • Proactor性能更高,能够处理耗时长的并发场景;

缺点

  • Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;

  • Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现;

适用场景

  • Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;

  • Proactor:异步接收和同时处理多个服务请求的事件驱动程序;

为什么Redis是单线程?

反应堆模型开发效率上比起直接使用IO复用要高,它通常是单线程的,设计目标是希望单线程使用一颗CPU的全部资源,但也有附带优点,即每个事件处理中很多时候可以不考虑共享资源的互斥访问。可是缺点也是明显的,现在的硬件发展,已经不再遵循摩尔定律,CPU的频率受制于材料的限制不再有大的提升,而改为是从核数的增加上提升能力,当程序需要使用多核资源时,反应堆模型就会悲剧,为何呢?
如果程序业务很简单,例如只是简单的访问一些提供了并发访问的服务,就可以直接开启多个反应堆,每个反应堆对应一颗CPU核心,这些反应堆上跑的请求互不相关,这是完全可以利用多核的。例如Nginx这样的http静态服务器。
如果程序比较复杂,例如一块内存数据的处理希望由多核共同完成,这样反应堆模型就很难做到了,需要昂贵的代价,引入许多复杂的机制。所以,大家就可以理解像redis、nodejs这样的服务,为什么只能是单线程,为什么memcached简单些的服务确可以是多线程。

参考

https://www.cnblogs.com/xiexj/p/6874654.html
http://www.taohui.org.cn/2016/01/27/%E9%AB%98%E6%80%A7%E8%83%BD%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B6-reactor%E5%8F%8D%E5%BA%94%E5%A0%86%E4%B8%8E%E5%AE%9A%E6%97%B6%E5%99%A8%E7%AE%A1%E7%90%86/

-------------本文结束感谢您的阅读-------------