Netty学习笔记

Netty是什么

Netty是一个高性能的、异步的、基于事件驱动的网络应用框架。

  • 核心:
    可扩展的事件模型
    统一的通信api
    无论是http 还是socket都使用统一的api,简化了操作
    零拷贝机制与字节缓冲区

  • 传输服务
    支持socket 以及datagram(数据报)
    持http协议
    In-VM Pipe (管道协议)

  • 协议支持
    http 以及 websocket
    SSL 安全套接字协议⽀持
    Google Protobuf (序列化框架)
    持zlib、gzip压缩
    持大文件的传输
    RTSP(实时流传输协议,是TCP/IP协议体系中的⼀个应⽤层协议)
    持二进制协议并且提供了完整的单元测试

Netty优势

  • Netty是基于Java的NIO实现的,Netty将各种传输类型、协议的实现API进行了统一封装,实现了阻塞和非阻塞Socket。
  • 基于事件模型实现,可以清晰的分离关注点,让开发者可以聚焦业务,提升开发效率。
  • 高度可定制的线程模型-单线程、一个或多个线程池,如SEDA(Staged Event-DrivenArchitecture)
    SEDA:把⼀个请求处理过程分成⼏个Stage,不同资源消耗的Stage使⽤不同数量的线程来处理,Stage间使⽤事件驱动的异步通信模式。
  • Netty只依赖了JDK底层api,没有其他的依赖,如:Netty 3.X依赖JDK5以上,Netty4.x依赖JDK6以上。
  • Netty在网络通信方面更加的高性能、低延迟,尽可能的减少不必要的内存拷贝,提高性能。
  • 在安全方面,完整的SSL/TLS和StartTLS支持。
  • 社区比较活跃,版本迭代周期短,发现bug可以快速修复,新版本也会不断的加入。

为什么选择Netty,不选择原⽣的NIO?

  • NIO的类库和API繁杂,使用麻烦,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
  • 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和⽹路编程⾮常熟悉,才能编写出高质量的NIO程序。
  • 可靠性能力补齐,工作量和难度都非常大。例如客户端⾯临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,
    NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。
  • JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK 1.6版本的update18修复了该问题,
    但是直到JDK 1.7版本该问题仍旧存在,只不过该BUG发生概率降低了而已,它并没有得到根本性解决。

Netty的⾼性能设计

要想了解netty性能高在哪儿,需要从java的IO模型聊起

java中的IO模型

  • 在JDK1.4之前,基于Java所有的socket通信都采用了同步阻塞模型(BIO),这种模型性能低下,当时大型的服务均采用C或C++开发,
    因为它们可以直接使用操作系统提供的异步IO或者AIO,使得性能得到大幅提升。
  • 2002年,JDK1.4发布,新增了java.nio包,提供了许多异步IO开发的API和类库。新增的NIO,极大的促进了基于Java的异步非阻塞的发展和应用。
  • 2011年,JDK7发布,将原有的NIO进行了升级,称为NIO2.0,其中也对AIO进进了支持。

BIO模型

java中的BIO是blocking I/O的简称,它是同步阻塞型IO,其相关的类和接口在java.io下。
BIO模型简单来讲,就是服务端为每一个请求都分配一个线程进⾏处理,如下:

NIO模型

NIO,称之为New IO 或是 non-block IO (非阻塞IO),NIO相关的代码都放在了java.nio包下,其三大核心组件:Buffer(缓冲区)、Channel(通道)、Selector(选择器/多路复⽤器)

  • Buffer
    在NIO中,所有的读写操作都是基于缓冲区完成的,底层是通过数组实现的,常用的缓冲区是ByteBuffer,
    每一种java基本类型都有对应的缓冲区对象(除了Boolean类型),如:CharBuffer、IntBuffer、LongBuffer等。
  • Channel
    在BIO中是基于Stream实现,在NIO中是基于通道实现,与流不同的是,通道是双向的,既可以读也可以写
  • Selector
    Selector是多路复用器,它会不断的轮询注册在其上的Channel,如果某个Channel上发生读或写事件,这个Channel就处于就绪状态,
    会被Selector轮询出来,然后通过SelectionKey获取就绪Channel的集合,进行IO的读写操作。

通过多路复用器就可以实现一个线程处理多个通道,避免了多线程之间的上下文切换导致系统开销过大。
NIO无需为每个连接开个线程处理,并且只有通道真正有有事件时,才进行读写操作,这样大大的减少了系统开销

AIO

在NIO中,Selector多路复用器在做轮询时,如果没有事件发生,也会进行阻塞,如何能把这个阻塞也优化掉呢?那么AIO就在这样的背景下诞生了。
AIO是asynchronous I/O的简称,是异步IO,该异步IO是需要依赖于操作系统底层的异步IO实现。
AIO的基本流程是:用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。
kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。

AIO模型存在的不足:

  • 需要完成事件的注册与传递,需要底层操作系统提供大量的支持,去做大量的工作。
  • Windows 系统下通过 IOCP 实现了真正的异步 I/O。但是,Windows 系统,很少作为百万级以上或者说高并发应用的服务器操作系统来使用。
  • 在 Linux 系统下,异步IO模型在2.6版本才引入,目前并不完善。所以,这也是在 Linux 下,实现高并发网络编程时都是以 NIO 多路复用模型模式为主。

Reactor线程模型

Reactor线程模型不是Java专属,也不是Netty专属,它其实是一种并发编程模型,是一种思想,具有指导意义。
列如,Netty就是结合了NIO的特点,应用了Reactor线程模型所实现的。

Reactor模型中定义的三种角色:
Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler。新的事件包含连接建立就绪、读就绪、写就绪等。
Acceptor:处理客户端新连接,并分派请求到处理器链中。
Handler:将自身与事件绑定,执行非阻塞读/写任务,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel。

常见的Reactor线程模型有三种,如下:
Reactor单线程模型
Reactor多线程模型
主从Reactor多线程模型

单Reactor单线程模型

说明:

  • Reactor充当多路复用器角色,监听多路连接的请求,由单线程完成
  • Reactor收到客户端发来的请求时,如果是新建连接通过Acceptor完成,其他的请求由Handler完成。
  • Handler完成业务逻辑的处理,基本的流程是:Read --> 业务处理 --> Send 。

优点
结构简单,由单线程完成,没有多线程、进程通信等问题。
适合用在一些业务逻辑比较简单、对于性能要求不高的应用场景。
缺点
由于是单线程操作,不能充分发挥多核CPU的性能。
当Reactor线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,
这更加重Reactor线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈。
可靠性差,如果该线程进⼊死循环或意外终止,就会导致整个通信系统不可用,容易造成单点故障。

单Reactor多线程模型

说明:
在Reactor多线程模型相比较单线程模型而言,不同点在于,Handler不会处理业务逻辑,只是负责响应用户请求,真正的业务逻辑,在另外的线程中完成。
这样可以降低Reactor的性能开销,充分利用CPU资源,从而更专注的做事件分发工作了,提升整个应用的吞吐。

但是这个模型存在的问题:
多线程数据共享和访问比较复杂。如果子线程完成业务处理后,把结果传递给主线程Reactor进进发送,就会涉及共享数据的互斥和保护机制。
Reactor承担所有事件的监听和响应,只在主线程中运,可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。
为了解决性能问题,产生了第三种主从Reactor多线程模型。

主从Reactor多线程模型

在主从模型中,将Reactor分成2部分:

MainReactor负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给SubReactor。
SubReactor主要完成和建立起来的socket的数据交互和事件业务处理操作。

该模型的优点:
响应快,不必为单个同步事件所阻塞,虽然Reactor本身依然是同步的。
可扩展性强,可以方便地通过增加SubReactor实例个数来充分利用CPU资源。
可复用性高,Reactor模型本身与具体事件处理逻辑无关,具有很高的复用性。

Netty模型

说明
在Netty模型中,负责处理新连接事件的是BossGroup,负责处理其他事件的是WorkGroup。Group就是线程池的概念。
NioEventLoop表示一个不断循环的执行处理任务的线程,用于监听绑定在其上的读/写事件。
通过Pipeline(管道)执行业务逻辑的处理,Pipeline中会有多个ChannelHandler,真正的业务逻辑是在ChannelHandler中完成的。

Netty核心组件

channel

Channel可以理解为是socket连接,在客户端与服务端连接的时候就会建立一个Channel,它负责基本的IO操作,列如:bind()、connect(),read(),write() 等。
Netty 的Channel接口所提供的 API,大大地降低了直接使用Socket类的复杂性。
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,常用的 Channel 类型:

NioSocketChannel,NIO的客户端 TCP Socket 连接。
NioServerSocketChannel,NIO的服务器端 TCP Socket 连接。
NioDatagramChannel, UDP 连接。
NioSctpChannel,客户端 Sctp 连接。
NioSctpServerChannel,Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件IO。

EventLoop、EventLoopGroup

有了 Channel 连接服务,连接之间可以消息流动。如果服务器发出的消息称作“出站”消息,服务器接受的消息称作“入站”消息。那么消息的“出站”/“入站”就会产生事件(Event)。
例如:连接已激活;数据读取;用户事件;异常事件;打开链接;关闭链接等等。
有了事件,就需要一个机制去监控和协调事件,这个机制(组件)就是EventLoop。 在 Netty 中每个 Channel 都会被分配到一个 EventLoop。一个 EventLoop 可以服务于多个 Channel。
每个 EventLoop 会占用一个 Thread,同时这个 Thread 会处理 EventLoop 上面发生的所有 IO 操作和事件。
EventLoopGroup 是用来生成 EventLoop

总结:
一个 EventLoopGroup 包含一个或者多个 EventLoop;
一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
一个 Channel 在它的⽣命周期内只注册于一个 EventLoop;
一个 EventLoop 可能会被分配给一个或多个 Channel。

ChannelHandler

ChannelHandler是最重要的组件,因为对于数据的入站和出站的业务逻辑的编写都是在ChannelHandler中完成的。
在前面的例子中,MyChannelHandler就是实现了channelRead方法,获取到客户端传来的数据。
对于数据的出站和入站,有着不同的ChannelHandler类型与之对应:
ChannelInboundHandler 入站事件处理器
ChannelOutBoundHandler 出站事件处理器

ChannelPipeline

在Channel的数据传递过程中,对应着有很多的业务逻辑需要处理,列如:编码解码处理、读写操作等,
那么对于每种业务逻辑实现都需要有个ChannelHandler完成,也就意味着,一个Channel对应着多个ChannelHandler,
多个ChannelHandler如何去管理它们,它们的执行顺序又该是怎么样的,这就需要ChannelPipeline进行管理了。
一个Channel包含了一个ChannelPipeline,而ChannelPipeline中维护了一个ChannelHandler的列表。
ChannelHandler与Channel和ChannelPipeline之间的映射关系,由ChannelHandlerContext进行维护。

ChannelHandler按照加入的顺序会组成一个双向链表,入站事件从链表的head往后传递到最后一个ChannelHandler,
出站事件从链表的tail向前传递,直到最后一个ChannelHandler,两种类型的ChannelHandler相互不会影响。

Bootstrap

Bootstrap是引导的意思,它的作用是配置整个Netty程序,将各个组件都串起来,最后绑定端扣、启动Netty服务。
Netty中提供了2种类型的引导类,一种用于客户端(Bootstrap),而另一种(ServerBootstrap)用于服务
器。

它们的区别在于:
ServerBootstrap 将绑定到某个端口,因为服务器必须要监听连接,Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。
引导客户端只需要一个EventLoopGroup,但是一个ServerBootstrap则需要两个。
因为服务器需要两组不同的 Channel
第一组将只包含一个 ServerChannel,代表服务器已绑定到某个本地端口的正在监听的套接字。
第二组将包含所有已创建的用来处理传入客户端连接。

与ServerChannel相关联的EventLoopGroup 将分配一个负责为传入连接请求创建 Channel 的EventLoop。一旦连接被接受,第一个 EventLoopGroup 就会给它的 Channel 分配一个 EventLoop。

Future

Future提供了在操作完成时通知应用程序的方式。这个对象可以看作是异步操作的结果的占位符,它将在未来的某个时刻完成,并提供对其结果的访问。
JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。
这是非常繁琐的,所以 Netty 提供了自己的实现——ChannelFuture,用于在执行异步操作的时候使用。

ChannelFuture提供了几种额外的方法,这些方法使得我们能够注册一个或者多个ChannelFutureListener实例。
监听器的回调方法operationComplete(),将会在对应的操作完成时被调用 。然后监听器可以判断该操作是成功地完成了还是出错了。
每个 Netty 的出站 I/O 操作都将返回ChannelFuture,也就是说,它们都不会阻塞。 所以说,Netty完全是异步和事件驱动的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值