|
|
|
@ -3,7 +3,7 @@
|
|
|
|
|
|
|
|
|
|
在基于传统同步阻塞模型开发中,ServerSocket 负责绑定IP 地址,启动监听端口,Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。
|
|
|
|
|
|
|
|
|
|
#### BIO通信模型
|
|
|
|
|
### BIO通信模型
|
|
|
|
|
通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,它接收到客户
|
|
|
|
|
端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。
|
|
|
|
|
|
|
|
|
@ -16,14 +16,14 @@
|
|
|
|
|
## 伪异步IO编程
|
|
|
|
|
为了解决 同步阻塞IO 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成 客户端个数M:线程池最大线程数N 的比例关系,其中 M 可以远远大于 N。通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
|
|
|
|
|
|
|
|
|
|
#### 伪异步IO模型图
|
|
|
|
|
### 伪异步IO模型图
|
|
|
|
|
采用线程池和任务队列可以实现一种叫做 伪异步的IO通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task对象 (该类实现了java.lang.Runnable接口),投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
|
|
|
|
|
|
|
|
|
|
![avatar](/images/Netty/伪异步IO通信模型.png)
|
|
|
|
|
|
|
|
|
|
伪异步 IO通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。
|
|
|
|
|
|
|
|
|
|
#### 伪异步IO编程弊端分析
|
|
|
|
|
### 伪异步IO编程弊端分析
|
|
|
|
|
要对 伪异步IO编程 的弊端进行深入分析,首先我们看两个 Java同步IO 的 API说明,随后结合代码进行详细分析。
|
|
|
|
|
```java
|
|
|
|
|
public abstract class InputStream implements Closeable {
|
|
|
|
@ -86,7 +86,7 @@ public abstract class OutputStream implements Closeable, Flushable {
|
|
|
|
|
与 Socket类 和 ServerSocket类 相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自
|
|
|
|
|
己的需要来选择合适的模式。一般来说,低负载、低并发的应用程序可以选择 同步阻塞IO,以降低编程复杂度;对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。
|
|
|
|
|
|
|
|
|
|
#### NIO类库简介
|
|
|
|
|
### NIO类库简介
|
|
|
|
|
NIO类库 是在 JDK 1.4 中引入的。NIO 弥补了原来 同步阻塞IO 的不足,它在 标准Java代码 中提供了高速的、面向块的IO。下面我们简单看一下 NIO类库 及其 相关概念。
|
|
|
|
|
|
|
|
|
|
**1、缓冲区Buffer**
|
|
|
|
@ -108,7 +108,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
|
|
|
|
|
|
|
|
|
|
一个 多路复用器Selector 可以同时轮询多个 Channel,由于 JDK 使用了 epoll() 代替传统的 select 的实现,所以它并没有最大连接句柄的限制。这也就意味着,只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。下面,我们通过 NIO编程的序列图 和 源码分析来熟悉相关的概念。
|
|
|
|
|
|
|
|
|
|
#### NIO服务端序列图
|
|
|
|
|
### NIO服务端序列图
|
|
|
|
|
|
|
|
|
|
![avatar](/images/Netty/NIO服务端序列图.png)
|
|
|
|
|
|
|
|
|
@ -189,7 +189,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
|
|
|
|
|
```
|
|
|
|
|
注意:如果发送区 TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入 TCP缓冲区。对于 “半包问题” 此处暂不赘述,后续会单独写一篇详细分析 Netty 的处理策略。
|
|
|
|
|
|
|
|
|
|
#### NIO 客户端序列图
|
|
|
|
|
### NIO 客户端序列图
|
|
|
|
|
|
|
|
|
|
![avatar](/images/Netty/NIO客户端序列图.png)
|
|
|
|
|
|
|
|
|
@ -299,13 +299,13 @@ NIO2.0 的异步套接字通道是真正的 异步非阻塞IO,对应于 UNIX
|
|
|
|
|
|
|
|
|
|
## 选择 Netty 开发项目的理由
|
|
|
|
|
从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。
|
|
|
|
|
#### 为什么不选择 Java原生NIO 进行开发
|
|
|
|
|
### 为什么不选择 Java原生NIO 进行开发
|
|
|
|
|
1. NIO 的类库和 API 使用起来非常繁杂,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。
|
|
|
|
|
2. 需要具备其他的额外技能做铺垫,例如,熟悉 Java多线程编程。这是因为 NIO编程 涉及到 Reactor模式,你必须对 多线程 和 网路编程 非常熟悉,才能编写出高质量的 NIO程序。
|
|
|
|
|
3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临:断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理,等问题。
|
|
|
|
|
4. JDK NIO 的 BUG,例如臭名昭著的 epoll bug,它会导致 Selector空轮询,最终导致 CPU 100%。虽然官方声称修复了该问题,但是直到 JDK 1.7版本 该问题仍旧未得到彻底的解决。
|
|
|
|
|
|
|
|
|
|
#### 为什么选择 Netty 进行开发
|
|
|
|
|
### 为什么选择 Netty 进行开发
|
|
|
|
|
Netty 是业界最流行的 NIO框架 之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC框架 Avro ,阿里的 RPC框架 Dubbo 就使用了 Netty 作为底层通信框架。通过对Netty的分析,我们将它的优点总结如下。
|
|
|
|
|
- API使用简单,开发门槛低;
|
|
|
|
|
- 功能强大,预置了多种编解码功能,支持多种主流协议;
|
|
|
|
|