Netty

Posted by 余腾 on 2020-05-11
Estimated Reading Time 7 Minutes
Words 2.1k In Total
Viewed Times

一、Netty

  • Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络IO程序。
  • Netty主要针对在 TCP 协议下,面向 Client 端的高并发应用,或者 Peer-to-Peer 场景下的大量数据持续传输的应用。
  • Netty 本质是一个 NIO 框架,适用于服务器通讯相关的多种应用场景。
  • Netty 是由 JBOSS 提供的一个Java 开源框架。Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序;

TCP/IP —> JDK/IO —> NIO —> Netty

BIO/NIO/AIO

  • Java BIO:同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
  • Java NIO :JDK1.4,同步非阻塞,服务器实现模式为一个线程处理多个请求(连接), 即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
  • Java AIO(NIO.2):异步非阻塞,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

零拷贝(无 CPU Copy)

​ 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有kernel buffer有一份数据)。零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算。

​ 传统IO,要经过,三次状态切换(用户态→内核态→用户态→内核态),四次拷贝;

零拷贝优化

mmap适合小数据量读写,sendFile 适合大文件传输。

  • MMap(内存映射)

    • mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。
  • sendFile:DMA (直接内存拷贝)

    • Linux2.1版本提供了sendFile函数,其基本原理:数据根本不经过用户态,直接从内核缓冲区进入到SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换。
    • Linux在2.4版本中,做了一些修改,避免了从内核缓冲区拷贝到Socketbuffer的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。这里其实有一次cpu 拷贝kernel buffer -> socket buffer但是,拷贝的信息很少,比如 lenght,offset,消耗低,可以忽略。

二、Netty 概述

  • NIO的类库和API繁杂,使用麻烦:需要熟练掌握Selector 、ServerSocketChannel 、SocketChannel、ByteBuffer等。
  • 需要具备其他的额外技能:要熟悉Java 多线程编程,因为NIO编程涉及到Reactor 模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。
  • 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流
    的处理等等。
  • JDK NIO的Bug;例如臭名昭著的 Epoll Bug,它会导致Selector 空轮询,最终导致CPU 100%。直到JDK 1.7
    版本该问题仍旧存在,没有被根本解诀。

线程模型

不同的线程模式,对程序的性能有很大影响,Netty线程模式(Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor)

目前存在的线程模型有:

  • 传统阻塞I/0服务模型
    • Reactor 模式
  • 根据 Reactor 的数量和处理资源池线程的数量不同,有3种典型的实现
    • 单 Reactor 单线程
      • 优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在-个线程中完成。
      • 缺点:
        • 性能问题:只有一个线程,无法完全发挥多核CPU的性能。Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈。
        • 可靠性问题:线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
      • 使用场景:客户端的数量有限,业务处理非常快速,Redis业务处理的时间复杂度O(1)的情况使用。
    • 单 Reactor 多线程
      • 优点:可以充分的利用多核cpu的处理能力。
      • 缺点:多线程数据共享和访问比较复杂,reactor处理所有的事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈。
    • 主从Reactor 多线程
      • 优点
        • 父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线
          程完成后续的业务处理。
        • 父线程与子线程的数据交互简单,Reactor 主线程只需要把新连接传给子线
          程,子线程无需返回数据。
      • 缺点:编程复杂度较高

Reactor 模式

针对传统阻塞I/O服务模型的2个缺点,解决方案:

  • 1、基于 I/O 复用模型:多个连接共用一个阻塞对象,应用程序只需要在一-个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理;
  • 2、基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。

Netty 模型

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

异步模型

基本介绍

  • 1、异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态、通知和回调来通知调用者。
  • 2、Netty 中的I/0操作是异步的,包括Bind、Write、 Connect 等操作会简单的返回一-个ChannelFuture;
  • 3、 调用者并不能立刻获得结果,而是通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得I0操作结果;
  • 4、Netty 的异步模型是建立在future和callback 的之上的。callback 就是回调。重点说Future,它的核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回显然不合适。那么可以在调用fun 的时候,立马返回一个Future,后续可以通过Future去监控方法fun的处理过程(即: Future-Listener 机制);

Future-Listener 机制

  • 当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操 作执行的状态,注册监听函数来执行完成后的操作。
  • 常见有如下操作
    • 通过isDone方法来判断当前操作是否完成;
    • 通过isSuccess 方法来判断已完成的当前操作是否成功;
    • 通过getCause方法来获取已完成的当前操作失败的原因;
    • 通过isCancelled方法来判断已完成的当前操作是否被取消;
    • 通过addLlistener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器;如果Future对象已完成,则通知指定的监听器。

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !