zoukankan      html  css  js  c++  java
  • Netty原理浅析

    一、Netty简介

    1、Netty是异步的、基于事件驱动的网络应用框架,它以高性能、高并发著称。基于事件驱动,简单点说就是 Netty 会根据客户端的连接请求、读、写等事件 做出相应的响应。

    2、Netty 主要用于开发基于 TCP 协议的网络 IO 程序。例如构建高性能RPC,实现高性能服务器/客户端程序等等。同时Netty也支持UDP、HTTP、WebSocket等多种主流协议。

    3、Netty 是基于 Java NIO 构建出来的,NIO是指非阻塞式IO,利用它可以提升并发能力

    图1 是Netty的功能特性图

    1、在传输服务方面:它支持TCP UDP传输; 支持HTTP 隧道等

    2、在协议支持方面: 它支持多种协议如HTTP WebSocket。 并且它提供了一些开箱即用的协议 例如可以用其提供的SSL 方便的进行认证与数据加密解密,利用其提供的zlib/gzip 可以方便的进行数据的压缩和解压缩,并且支持了google的protobuf序列化方式。并且支持大文件传输,实时的流传输

    3、它的核心功能包括三方面:

    3.1利用其提供的可拓展事件模型,我们可以方便的添加自己的业务逻辑

    3.2利用其提供的通用通信API,我们可以告别java NIO 的繁琐 复杂的代码

    3.3支持零拷贝,零拷贝可以减少数据在内存中的拷贝,可以大幅提高IO性能

    1、首先Netty可以用于分布式应用开发中,Netty 作为异步高并发的网络组件,常用于构建高性能 RPC 框架,以提升分布式服务群之间的服务调用或数据传输的并发度和速度。例如阿里 Dubbo 就可以使用 Netty 作为其网络层

    2、Netyy还可以用于大数据基础设施的构建:比如 Hadoop在处理海量数据的时候,数据在多个计算节点之中传输,为了提高传输性能,也采用 Netty 构建性能高的网络 IO 层

    3、用Netyy还可以实现 应用层基于公有协议或私有协议的服务器

    二、Netty原理

    零拷贝技术

      1)  Netty 利用了零拷贝技术 提升了IO 性能

      2)   零拷贝指的是 数据在内存中的拷贝次数为0次

      3)  图2 代表了 磁盘中的一个数据 发给网络的过程,如果不利用零拷贝 磁盘的数据要先拷贝到内核缓冲区,再拷贝到应用程序内存,再拷贝到Socket缓冲区,最后再发向网络。不利用零拷贝,数据在内存中拷贝了两次,一次是内核缓冲区到用户程序内存,另一次是应用程序内存到Socket缓冲区。

    而零拷贝技术,将内核缓冲区 与 应用程序内存 和Socket缓冲区建立了地址映射,这样数据在内存中的拷贝次数就是0次,减少了拷贝次数,可以大幅提升IO性能。

    1) Netty 是基于NIO的,NIO的特点是可以利用一个线程,并发处理多个连接 也称为IO多路复用

    2) 图3是 NIO 的示意图,服务器中一个线程可以非阻塞地处理多个客户端的IO请求。具体过程为服务器为每个客户端 分配Channel和Buffer,数据是通过通道 Channel 传输的,往Channel中读写数据需要先经过缓冲区Buffer。接着将每个客户端对应的Channel的IO事件注册到多路复用器 Selector上,Selector通过轮询,就可以找到有IO活动的channel并进行处理,这就是NIO的具体流程。以这种IO处理模式也称为Reactor模式。

    3) 这种模式非阻塞的原因是:若某通道无可用数据,线程不会阻塞在这个通道上等数据准备好,而是可以处理其他通道的读写。而传统的阻塞式IO,采用一个线程对应一个客户端的方式,若客户端数据未准备好,则线程一直阻塞。传统的阻塞式IO,线程利用率不高,且高并发是需要建立大量的线程。而NIO降低了线程数量,提高了线程的利用率 实现了IO 多路复用。Netty 正是利用这种非阻塞式的IO,实现了单个线程就可以并发处理多个连接。

    Channel 通道:

    1)数据是通过通道传输的,它为应用提供I/O操作接口,定义了与socket交互的操作集 比如读、写、连接、绑定等。

    2)表1是一些常用的 Channel 类型,不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,,TCP连接中客户端和服务器用不同的Channel,linux下可以使用EpollSocketChannel建立非阻塞的TCP连接,它是用linux的epoll命令实现的 效率更高。

    1)ChannelHandler 通道处理接口:传递到通道的数据或者通道传来的数据要利用ChannelHandler进行处理,例如可以进行编码、解码、加密、解密等

    2) Netty 中流向Chnannel的有两个方向的数据,入站数据指的是从网络发至客户端或者服务器的数据;出站数据指的是 客户端或服务器 发到网络中的数据。

    3) 因此也有两个方向的通道处理接口,ChannelInboundHanlder 继承自ChanelHandler 专门用于处理入站数据

    4) ChanneloutboundHandler 处理出站数据

    5)  编码器都继承了ChanneloutboundHandler 因为发向网络的数据一般要先经过编码,比如说要将对象转化成字节序列,再在网络中传输。解码器都继承了ChannelintboundHandler,因为需要将字节序列转化成对象。同理,加密继承于ChanneloutboundHandler,解密继承于ChannelintboundHandler。

    1) 数据处理链是包含多个ChannelHandler的双向链表。图5 是ChannelPipline的示意图,从网络中接收的数据从左边的Socket中传入ChannelPipline,入站的时候从链表头部,依次传入所有的ChannelInboundHandler中进行处理。出站的从链表尾部依次传入所有的CahnneloutboundHandler进行处理。

    2、 ChannelPipeline其实就是一种高级形式的拦截过滤器。我们可以方便的增加删除ChannelPipline中的ChannelHanlder,也可以自己实现ChannelHandler,这样就能完全控制数据从入站到出战的处理方式,以及各个ChannelHandler 之间的相互交互方式。

    1) 一个事件循环对应一个线程,如图6所示,一个事件循环内维护了一个多路复用器,selector,和一个任务队列taskQueue。

    2) 服务器给每个客户端分配一个通道Channel,并将该通道的IO事件注册到Selector上,Selector 用于轮询各个Channel的IO事件

    3) 任务队列可以异步执行提交的IO任务与非IO任务任务,还可以执行定时任务,比如说我们可以利用任务队列,向给建立连接的客户端定时发消息。

    如图6所示 EventLoop 其实就循环执行三件事情

    1、轮询注册在selector上的channel的IO事件

    2、在对应的Channel处理IO事件

    3、执行任务队列中的任务

    每个EventLoop可以负责处理多个Channel上的事件

    一个Channel只对应于一个EventLoop (防止并发操作 出现Bug)

    5) EvenLoopGroup 事件循环组

    EvenLoopGroup中含有多个的EventLoop

    可以简单理解为一个线程池,内部维护了一组线程,

    EvenLoopGroup 默认初始化 CPU核心数*2 个EventLoop

     6) Bootstrap 引导类

    一个Netty应用由一个Bootstrap开始,主要是用来配置整个 Netty 程序、设置业务处理类(Handler)、绑定端口、发起连接等

    7) ChannelFuture 异步结果占位符

    Netty的I/O操作是异步的,操作可能无法立即返回

    ChannelFuture对象作为 异步操作结果的占位符 可确定异步执行的结果

    通过addListener方法 可注册了一个监听ChannelFutureListener,当操作完成时,自动触发注册的监听事件

    图7 是Netty 服务端的工作架构图: 该图中有两个事件循环组:BossGroup 和 WorkerGroup,BossGroup 中的事件循环专门和客户端建立连接,WorkerGroup 中的EventLoop专门负责处理连接上的读写。

    在这里,我通过模拟一个客户端给服务器发消息来解释图7:

    1、首先初始化ServerSocketChannel 并将建立连接的事件Accept,注册到BoosGroup的一个事件循环的Selector上

    2、接着事件循环就会轮询Channel上的建立连接事件

    3、一个客户端 发来建立连接请求后,Seletor通过轮询可以发现此请求,并通过processSeleterKeys 处理处理连接请求

    4、怎么处理连接请求呢?首先是为这个连接分配一个SocketChannel,并将这个Channel的读写事件注册到一个WorkerGroup的事件循环的selector上,这时连接就建立好了,并且WorkerGroup会轮询SocketChannel的读写事件。

    5、当这个客户端再发送消息时,事件循环会轮询到写事件,并通过processSeleterKeys处理消息

    6、processSeleterKeys通过刚刚讲的数据处理链过ChannelPipline来进行处理,可能包含先解码、再进行业务处理,再编码,再发送到SocketChannel中。

    以上是服务端的具体流程,客户端也会建立一个Channel ,也有一个Seletor轮询IO事件,当消息到达时,也可以通过客户端的ChannelPipline进行处理。

    到现在,我们已经大概了解了Netty的工作原理,BoosGroup 用于专门创建连接,其中有多个事件循环线程,每个事件循环都监听对应通道的建立连接请求并进行处理。WorkGroup 中也有多个事件循环线程,负责对应通道的IO事件。一个线程可以负责多个通道的IO,实现了IO多路复用。

    建立连接、IO处理都由多个线程去做,提高了并发能力,也提高了系统的可靠性 (在之前的单线程处理IO的情况下 若意外终止 则服务不可用)。

    三、ByteBuf和引用计数

    Netty 利用ByteBuf作为缓冲区,利用Channel进行读写都要经过缓冲区,因此需要了解ByteBuf 的基本概念和操作才能更好的利用Netty编程。

    ByteBuf  是存储字节的容器 类似于NIO中的 ByteBuffer

    ByteBuf 中存在

    1、写索引: writerIndex   (当数据写入ByteBuf时 writerIndex增加)

    2、读索引: readerIndex   (当从ByteBuf读数据时 readerIndex增加)

    当writerIndex==readerIndex时 :代表无数据可以读

    capacity (ByteBuf的容量):默认为Integer.MAX_VALUE

    因此可将ByteBuf 分为 三个部分

    1. 可以被丢弃字节

    2. 可读字节

    3. 可写字节

    ByteBuf 共有三种使用模式

    模式1:Heap Buffer(堆缓冲区)

    它是将数据存储在JVM的堆空间(通过将数据存储在数组中实现)

     堆缓冲区可以通过JVM快速分配与释放

     模式2 :Direct Buffer 直接缓冲区

    不在JVM的堆中分配内存,而是在JVM外通过本地方法调用分配虚拟机外内存

    优点:免去中间交换的内存拷贝,提升IO处理速度:若在堆,则需要将数据先复制到直接缓冲区,再复制到堆 这体现了Netty的零拷贝特性

    模式3:Composite Buffer 复合缓冲区

    是一种视图,不实际存数据,它可以由多个堆缓冲区和直接缓冲区 复合组成

    优点:可将消息拆分为多个部分,若某部分不变,则不用每次都分配新的缓冲区存不变的部分(向多个客户端发 相同的消息body不变 header变  可以复用body)

    有两种ByteBuf 分配方式,

    1.一种是通过ByteBufAllcator类,它可以分配池化或者池化的ByteBuf实例,利用池化技术可以改进性能 降低内存使用率。可以通过channel 和channelhandlercontext 获得该实例,代码如图11所示。
    2.第二种分配方式是利用Unpooled类提供的静态方法,可以创建非池化的ByteBuf实例
     
     

    上面我们讲到,ByteBuf可以利用直接内存避免拷贝数据到用户空间,并且Netty还使用池化技术降低内存使用率。因为用到了池化技术,Netty需要将用完的对象放回池中,java的垃圾回收器无法完成此功能,因此引入了引用计数,将用完的对象放回池中。

    如图所示 每个对象的初始引用计数为1

    buf.retain() ,buf 引用计数加1

    buf.release()  ,buf引用计数减1

    当引用计数为0时 释放对象,并返回对象池。

    ByteBuf引用计数的原则是:谁最后使用,谁负责释放

    Netty提供了检查内存泄漏的方式,通过配置JVM 的leakDetectionLevel 可以开启指定级别的泄漏检测

    默认是简单级别,它会抽样百分之1的样本,并告诉我们是否发生内存泄漏。

    高级级别可以告诉我们内存泄漏发生的地方。

    偏执级别会检测所有样本。

    参考资料:

    Netty官网

    Netty的架构与原理初探

    理解高性能网络模型

    Netty试题

    Netty实战精髓

    Netty之有效规避内存泄漏

  • 相关阅读:
    解惑如何保证数组元素的可见性(yet)
    为什么内存锁在有事务切面的情况下会形同虚设 隔离级别与事务
    小事故合集
    第4种打整包插件,urlfactory already set
    三目运算符与字节码阅读
    servlet application/x-www-form-urlencoded 坑
    多实例重复发邮件
    当动态代理遇到ioc (五)使用cglib切面与自定义类加载器构建独有环境aop日志
    mac笔记本如何重制secureCRT的体验期|试用版本
    npm install出现的错误
  • 原文地址:https://www.cnblogs.com/lancelee98/p/15336698.html
Copyright © 2011-2022 走看看