第1章 --- netty的nio概念-线程模型

目录

Netty网络I/O

Linux IO

阻塞IO的通信方式(BIO)

I/O 复用模型

非阻塞IO多路复用(NIO)​

线程模型

事件驱动模型

Reactor 线程模型

Netty 线程模型

3 异步处理

传统IO/Netty对比

JDK 原生 NIO 程序的问题

Netty的主要特点有:


Netty网络I/O

Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高,两张图让你了解BIO和NIO的区别:

Linux IO

阻塞IO的通信方式(BIO)

 

I/O 复用模型

非阻塞IO多路复用(NIO)

 

l 从这两图可以看出,NIO的单线程能处理连接的数量比BIO要高出很多,而为什么单线程能处理更多的连接呢?原因就是图二中出现的Selector。

当一个连接建立之后,他有两个步骤要做,

第一步是接收完客户端发过来的全部数据,

第二步是服务端处理完请求业务之后返回response给客户端。NIO和BIO的区别主要是在第一步。

在BIO中,等待客户端发数据这个过程是阻塞的,这样就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因。

 

l IO多路复用

在 I/O 复用模型中,会用到 Select,这个函数也会使进程阻塞,但是和阻塞 I/O 所不同的是这两个函数可以同时阻塞多个 I/O 操作。

而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。

 

l 非阻塞IO多路复用

Netty 的非阻塞 I/O 的实现关键是基于 I/O 复用模型,这里用 Selector 对象表示:

就是select、poll、epoll,多路复用的好处就在于单个进程就可以同时处理多个网络连接的IO。它的基本原理就是select、poll、epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程(其实更准确的说是一个用户进程的selector注册多个socket,任何一个socket中数据准备好了,selector都会知道,当用户进程调用selector的select方法时,会得到所有数据准备好的sockets,用户进程再调用read操作,将数据从kernel拷贝到用户进程)。

其他解释:NIO中,当一个Socket建立好之后,Thread并不会阻塞去接受这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历所有的Socket,一旦有一个Socket建立完成,他会通知Thread,然后Thread处理完数据再返回给客户端——这个过程是阻塞的,这样就能让一个Thread处理更多的请求了。

 

【基于 Buffer】:

l 传统的 I/O 是面向字节流或字符流的,以流式的方式顺序地从一个 Stream 中读取一个或多个字节, 因此也就不能随意改变读取指针的位置。

n 在 NIO 中,抛弃了传统的 I/O 流,而是引入了 Channel 和 Buffer 的概念。在 NIO 中,只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel。

n 基于 Buffer 操作不像传统 IO 的顺序操作,NIO 中可以随意地读取任意位置的数据。

线程模型

数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,线程模型的不同,对性能的影响也非常大。

事件驱动模型

通常,我们设计一个事件处理模型的程序有两种思路:

1)轮询方式:线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑;

2)事件驱动方式:发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,其实是设计模式中观察者模式的思路。

以 GUI 的逻辑处理为例,说明两种逻辑的不同:

1)轮询方式:线程不断轮询是否发生按钮点击事件,如果发生,调用处理逻辑。

2)事件驱动方式:发生点击事件把事件放入事件队列,在另外线程消费的事件列表中的事件,根据事件类型调用相关事件处理逻辑。

这里借用 O'Reilly 大神关于事件驱动模型解释图:

主要包括 4 个基本组件:

1)事件队列(event queue):接收事件的入口,存储待处理事件;

2)分发器(event mediator):将不同的事件分发到不同的业务逻辑单元;

3)事件通道(event channel):分发器与处理器之间的联系渠道;

4)事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作。

 

可以看出,相对传统轮询模式,事件驱动有如下优点:

1)可扩展性好:分布式的异步架构,事件处理器之间高度解耦,可以方便扩展事件处理逻辑;

2)高性能:基于队列暂存事件,能方便并行异步处理事件。

Reactor 线程模型

Reactor 是反应堆的意思,Reactor 模型是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。

服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor 模式也叫 Dispatcher 模式,即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术之一。

Reactor 模型中有 2 个关键组成:

1)Reactor:Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;

2)Handlers:处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。

 

取决于 Reactor 的数量和 Hanndler 线程数量的不同,Reactor 模型有 3 个变种:

1)单 Reactor 单线程;

2)单 Reactor 多线程;

3)主从 Reactor 多线程。

可以这样理解,Reactor 就是一个执行 while (true) { selector.select(); …} 循环的线程,会源源不断的产生新的事件,称作反应堆很贴切。

篇幅关系,这里不再具体展开 Reactor 特性、优缺点比较,有兴趣的读者可以参考我之前另外一篇文章:《高性能网络编程(五):一文读懂高性能网络编程中的I/O模型》、《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》。

 

Netty 线程模型

Netty 主要基于主从 Reactors 多线程模型(如下图)做了一定的修改,其中主从 Reactor 多线程模型有多个 Reactor:

1)MainReactor 负责客户端的连接请求,并将请求转交给 SubReactor;

2)SubReactor 负责相应通道的 IO 读写请求;

3)非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理。

这里引用 Doug Lee 大神的 Reactor 介绍——Scalable IO in Java 里面关于主从 Reactor 多线程模型的图:

 

特别说明的是:虽然 Netty 的线程模型基于主从 Reactor 多线程,借用了 MainReactor 和 SubReactor 的结构。但是实际实现上 SubReactor 和 Worker 线程在同一个线程池中:


 

EventLoopGroup bossGroup = newNioEventLoopGroup();

EventLoopGroup workerGroup = newNioEventLoopGroup();

ServerBootstrap server = newServerBootstrap();

server.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class)

上面代码中的 bossGroup 和 workerGroup 是 Bootstrap 构造方法中传入的两个对象,这两个 group 均是线程池:

1)bossGroup 线程池则只是在 Bind 某个端口后,获得其中一个线程作为 MainReactor,专门处理端口的 Accept 事件,每个端口对应一个 Boss 线程;

2)workerGroup 线程池会被各个 SubReactor 和 Worker 线程充分利用。

 

 异步处理

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

 

Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。

调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。

当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作。

常见有如下操作:

1)通过 isDone 方法来判断当前操作是否完成;

2)通过 isSuccess 方法来判断已完成的当前操作是否成功;

 

3)通过 getCause 方法来获取已完成的当前操作失败的原因;

4)通过 isCancelled 方法来判断已完成的当前操作是否被取消;

5)通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器。

 

例如下面的代码中绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑:

serverBootstrap.bind(port).addListener(future -> {

       if(future.isSuccess()) {

           System.out.println(newDate() + ": 端口["+ port + "]绑定成功!");

       } else{

           System.err.println("端口["+ port + "]绑定失败!");

       }

   });

相比传统阻塞 I/O,执行 I/O 操作后线程会被阻塞住, 直到操作完成;异步处理的好处是不会造成线程阻塞,线程在 I/O 操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量。

传统IO/Netty对比

JDK 原生 NIO 程序的问题

JDK 原生也有一套网络应用程序 API,但是存在一系列问题,主要如下:

1)NIO 的类库和 API 繁杂,使用麻烦:你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。

2)需要具备其他的额外技能做铺垫:例如熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的 NIO 程序。

3)可靠性能力补齐,开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等。NIO 编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大。

4)JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。官方声称在 JDK 1.6 版本的 update 18 修复了该问题,但是直到 JDK 1.7 版本该问题仍旧存在,只不过该 Bug 发生概率降低了一些而已,它并没有被根本解决。

4、Netty 的特点

Netty 对 JDK 自带的 NIO 的 API 进行了封装,解决了上述问题。

 

Netty的主要特点有:

1)设计优雅:适用于各种传输类型的统一 API 阻塞和非阻塞 Socket;基于灵活且可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型 - 单线程,一个或多个线程池;真正的无连接数据报套接字支持(自 3.1 起)。

2)使用方便:详细记录的 Javadoc,用户指南和示例;没有其他依赖项,JDK 5(Netty 3.x)或 6(Netty 4.x)就足够了。

3)高性能、吞吐量更高:延迟更低;减少资源消耗;最小化不必要的内存复制。

4)安全:完整的 SSL/TLS 和 StartTLS 支持。

5)社区活跃、不断更新:社区活跃,版本迭代周期短,发现的 Bug 可以被及时修复,同时,更多的新功能会被加入。

 

Netty 常见使用场景

Netty 常见的使用场景如下:

1)互联网行业:在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。

2)游戏行业:无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈。

非常方便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以方便的通过 Netty 进行高性能的通信。

3)大数据领域:经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨界点通信,它的 Netty Service 基于 Netty 框架二次封装实现。

参考来源 https://www.cnblogs.com/imstudy/p/9908791.html 

发布了38 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/sdrfengmi/article/details/90671850