1 Netty出现的背景
java中网络编程,传统的一般都是利用java的网络io和网络nio有关的API。这些原始的API使用起来有如下的缺点:
- 使用起来很复杂,不方便用户使用
如下图所示,使用java.net包下的网络API来构建socket网络编程的过程。
该代码片段反映了java原始的网络API的问题有:
- 图中标注(1)的地方,重复写了很多的样板代码,写起来很繁琐
- 图中标注(2)的地方,对于每个客户端socket连接,都会创建一个新的线程来处理。每个线程的都需要分配内存,默认值为64KB到1MB。
- 当并发量很高时,且标注(3)的地方处理时间较长时,则会有大量的线程处于阻塞状态,只是等待输入或者输出数据;此外,创建大量线程,上下文切换的开销会显得很大。
-
不方便扩展
如果之前用户使用的是BIO,如上面的代码片段所示,此时要切换到NIO,则必须重新根据NIO API,再写一份完全不一样的代码。下面是一个使用NIO API的例子。
可以看出,java BIO API和java NIO API使用方式完全不一样,想要从BIO API切换到NIO API不是一件容易的事情。而且,NIO API也存在大量的样板代码,写起来很繁琐。 - 直接使用java提供的阻塞网络API,在高并发下性能存在问题;使用java提供的nio API,使用繁琐且容易出错
这一点在上面说过java BIO API在高并发下存在性能问题;NIO API写起来繁琐且易出错,比如ByteBuffer的flip()操作很不友好,导致用户很容易写错。
2 Netty要做的事情
- 方便用户使用的框架,有简单的API
- 扩展性更好的框架,方便用户在阻塞IO和非阻塞IO之间切换
- 规避java BIO API的性能问题
3 Netty是怎么做的
统一的API
- 封装样板代码,提供统一的API
- 抽象出组件,组件可以复用
详实的文档和示例
使用Reactor模式提高并发性能
下图是一个典型的多线程的Reactor模式的组件图。
图中有三个关键的组件:mainReactor,subReactor和Thread Pool线程池。我们知道,服务端接受网络请求有几个步骤:接受连接,等待数据传输,处理请求,返回响应数据。这三个组件分别处理不同的阶段。mainReactor负责接收客户端的连接并将其传递给subReactor。subReactor负责处理与客户端的读写请求。具体的业务处理逻辑,由Thread Pool线程池分配出不同的线程来进行处理。
优化内存管理
- 池化内存
一个伟大的中间件,看起来必然有一个可以由自己管理内存的内存管理器,例如kafka还有这里的netty。Netty内存池的实现参考了jemalloc的原理。 - 零拷贝技术
零拷贝技术是一个可以直接将内核缓冲区A的数据复制到内核缓冲区B的技术,不需要经过应用缓冲区做中转操作。后面的文章会介绍。
4 Netty最终解决了哪些问题
- 样板代码太多而不方便使用的问题
- 原始的API不方便扩展的问题
- 阻塞IO API性能太低的问题;非阻塞IO API不方便使用且容易出错的问题
上面说的那些,都将在后面的文章中具体讲到。