1.Netty概述
Netty是由JBoss提供的开源的高性能、异步事件驱动的网络应用框架,通过Netty可以很快的构建出通信效率极高的应用;
- 异步与同步是相对的,在请求或执行过程中,如果会阻塞等待就是同步,反之就是异步
1.1 Netty核心架构
Core(核心):
- 可扩展的事件模型;
- 统一的通信api;
- 零拷贝机制与字节缓冲区;
Transport Services(传输服务):
- 支持socket以及datagram(数据报,一种不可考的数据传输方式,常用于UDP传输);
- 支持http协议;
- 支持In-VM Pipe(管道协议,是jvm的一种进程);
Protocol Support(协议支持):
- http与websocket;
- SSL安全套接字协议支持;
- Google Protobuf(之前提到过的高性能序列化框架);
- 支持Zlib、gzip压缩;
- 支持大文件的传输;
- RTSP(实时流传输协议,应用层协议)
- 支持二进制协议并且提供了完成的单元测试;
1.2 Netty优点
- Netty基于Java的NIO实现,将各种传输类型、协议的实现API进行了统一封装,实现了阻塞和非阻塞Socket;
- 基于事件模型实现,可以清晰的分离关注点,让开发者可以聚焦业务,提升开发效率;
- 高度可定制的线程模型,单线程、一个或多个线程池(SEDA,把一个请求分成多个Stage处理,不同资源消耗的Stage使用不同数量的线程来处理,Stage处理过程中使用事件驱动的异步通信模式);
- Netty只依赖了JDK底层api,没有其他的依赖(Netty 3.X依赖JDK5以上,Netty 4.X依赖JDK6以上);
- Netty在网络通信方面更加的高性能、低延迟、尽可能的减少不必要内存拷贝,提高性能;
- 原生NIO可靠性较差,且API比较繁杂,使用需要对多线程和网络编程非常熟悉,NIO还存在BUG(如epoll bug会导致selector空轮询,导致cpu100%,Netty中使用轮询时间过短则重新创建selector来解决这个bug),jdk11之后bug少了很多;
1.3 Netty的使用场景
Netty的使用场景十分广泛,很多好用的框架都有用到Netty;
- ES:es中netty用来处理节点间的通信以及客户端的请求;
- Kafka:netty负责kafka中内部组件的通信;
- Dubbo:使用Netty作为其默认的通讯方式处理服务间的远程调用;
- gRPC:gRPC 的 Java 实现使用 Netty 作为其网络通信层;
- Nacos:使用 Netty 进行服务注册、发现和配置管理的网络通信;
- RocketMQ: 使用 Netty 进行消息的传输和处理;
2.Netty高性能架构
Netty之所以性能高,主要就是使用Java的NIO实现了Reactor线程模型;
2.1 Java的IO模型
JDK 4 之前基于Java所有的socket通信都采用了同步阻塞模型(BIO),性能低下;
JDK 4 新增了 java.nio包,提供了很多异步IO开发的API和类库,促进了基于Java的异步非阻塞的发展;
JDK 7 将原有的NIO进行了升级,其中也对AIO(异步非阻塞)进行了支持;
BIO不做多解释,高并发下性能过低,并发数与线程数1:1;
2.1.1 NIO模型
NIO(同步非阻塞IO),其三大核心组件:Buffer(缓冲区)、Channel(通道)、Selector(选择器/多路复用器);
- Buffer:在NIO中所有读写操作都是基于缓冲区完成的,底层是通过数组实现的,常用的缓冲区是ByteBuffer,每个Java基本类型都有对应的缓冲区对象(除Boolean),如CharBuffer、IntBuffer、LongBuffer等;
- Channel:在BIO中基于Stream实现,在NIO中基于通道实现,通道是双向的既能读也可写;
- Selector:轮询注册在其上的Channel,如果某个Channel上发生读写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey获取就绪Channel集合,进行IO读写操作;
2.1.2 AIO模型
在NIO中当Selector轮询中没有事件发生也会阻塞,AIO就是为了优化这个阻塞诞生的;
AIO(异步非阻塞IO)依赖于操作系统底层的异步IO实现;
AIO的基本流程:用户线程通过系统调用告知kernel内核启动某个IO操作,用户线程返回,kernel内核在整个IO操作(数据准备、数据复制)完成后,通知用户程序;
- 数据准备:将数据从网卡读取到内核缓冲区;
- 数据复制:将数据从内核缓冲区拷贝到用户程序空间的缓冲区;
AIO也存在一定不足:
- 需要完成事件的注册与传递,需要底层操作系统提供大量支持,去做大量的工作;
- Windows系统通过IOCP实现了真正的异步IO,但是目前大多高并发应用的服务器操作系统不使用;
- LInux系统下,主要使用 io_uring ,性能比AIO强;
2.2 Reactor线程模型
Reactor线程模型是一种用于处理并发IO操作的设计模式,常用于高性能网络应用程序,主要通过事件驱动机制来处理多个连接的请求,而不需要为每个连接分配一个线程;
- Reactor:负责监听和分配事件,将IO事件分派给对应的Handler,新的事件包含连接建立就绪、读就绪、写就绪;
- Acceptor:处理客户端新连接,并分派请求到处理器链中;
- Handler:将自身与事件绑定,执行非阻塞读写任务,完成Channel的读入,完成处理业务逻辑后,负责将结果写出Channel;
常见Reactor线程模型有三种:Reactor单线程模型、Reactor多线程模型、主从Reactor多线程模型;
2.2.1 Reactor单线程模型
- Reactor充当多路复用器角色,监听多路连接的请求,由单线程完成;
- Reactor收到客户端发来的请求时,如果是新建连接通过Acceptor完成,其他请求由Handler完成;
- Handler完成业务逻辑的处理;
优点:
- 结构简单,适合用在一些业务逻辑比较简单、对性能要求不高的应用场景;
缺点:
- 由于是单线程操作,不能发挥多核CPU的性能;
- 当Reactor线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重Reactor线程的负载,最终导致大量消息积压和处理超时;
- 可靠性差,如果该线程进入死循环或意外终止,就会导致整个通讯系统不可用,容易造成单点故障;
2.2.2 Reactor多线程模式
- 与单线程主要区别在Handler中,Reactor多线程模式Handler不会处理业务逻辑,只负责响应用户请求,业务逻辑由其他线程完成;
- 这样降低Reactor的性能开销,充分利用CPU资源,
缺点:
- 多线程数据共享和访问比较复杂,如果子线程完成业务处理后,把结果传递给主线程Reactor进行发送,就会涉及共享数据的互斥和保护机制;
- Reactor承担所有事件的监听和响应,只在主线程中运行,可能会存在性能问题;
2.2.3 主从Reactor多线程模型
在主从模型中,Reactor分成了两部分:
- MainReactor负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给SubReactor;
- SubReactor主要完成和建立起来的socket的数据交互和事件业务处理操作;
优点:
- 响应快,不必为单个同步事件阻塞,但Reactor本身依然是同步的;
- 可扩展性强,可以方便的通过增加SubReactor实例个数来充分利用CPU资源;
- 可复用性强,Reactor模型本身与具体时间处理逻辑无关,具有很高的复用性;
2.3 Netty模型
Netty模型基于Reactor模型实现,支持上面三种模型,一般采用主从架构模型;
- BossGroup负责连接事件,WorkGroup负责处理其他事件;
- NioEventLoop表示一个不断循环的执行处理任务的线程,用于监听绑定在其上的读写事件;
- 通过PIpeline执行业务逻辑的处理,Pipeline中会有多个ChannelHandler,ChannelHandler完成真正的业务逻辑;