高性能网络框架Netty介绍以及io模型

**

1、Netty是什么?

**
Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
它提供了对TCP、UDP和文件传输的支持。支持Http、websocket等协议,支持自定义协议栈。

2、Netty应用场景

作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。
Dubbo、RocketMQ、Hadoop,elasticSearch,还有开源集群运算框架 Spark、分布式计算框架 Storm、构建 JVM 上的并发应用和分布式应用 Akka,都采用了 Netty 作为通信基础。
java消费pulsar的jar包也是使用netty通信的。

**

3、Netty的优点

**
● PI使用简单,开发门槛低;
● 功能强大,预置了多种编解码功能,支持多种主流协议;
● 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
● 性能优秀,高吞吐、低延迟
● 可扩展性好,基于事件驱动模型、可自定义线程模型
● 支持非阻塞的socket
● 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入

发送websocket时,netty性能高20%

4、Netty性能高的原因

● 非阻塞IO:Netty采用了IO多路复用技术,Reactor主从多线程模型,能够有效的应对大量的并发请求,Client连接有很多,但是NIO 线程数是比较少的,一个NIO 线程可以同时绑定到多个Client,同时一个Client只能对应一个线程,避免出现线程安全问题
○ 主线程:Acceptor 线程池用于监听Client 的TCP 连接请求
○ 从线程:Client 的IO 操作都由一个特定的NIO 线程池负责,负责消息的读取、解码、编码和发送
● 无锁化串行设计:消息的处理尽可能在一个线程内完成,期间不进行线程切换,避免了多线程竞争和同步锁的使用
● 高效的并发编程实现:
○ volatile 的大量、正确使用
○ CAS 和原子类的广泛使用
○ 线程安全容器的使用
○ 通过读写锁提升并发性能
● 高性能的序列化框架:Netty 默认提供了对Google Protobuf 的支持,通过扩展Netty 的编解码接口,可以实现其它的高性能序列化框架
● 零拷贝:
○ Netty 的接收和发送ByteBuffer 采用DirectByteBuffer,使用堆外直接内存进行Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HeapByteBuffer)进行Socket 读写,JVM 会将堆内存Buffer 拷贝一份到直接内存中,然后才写入Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝
○ Netty 提供了组合Buffer 对象,可以聚合多个ByteBuffer 对象,用户可以像操作一个Buffer 那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer 合并成一个大的Buffer。
○ Netty 的文件传输采用了transferTo()方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write()方式导致的内存拷贝问题。
● 内存池:
○ 基于对象池的 ByteBuf可以重用 ByteBuf对象,内部维护了一个内存池,可以循环利用已创建的 ByteBuf,提升内存的使用效率,降低由于高负载导致的频繁GC。测试表明使用内存池后的Nety在高负载、大并发的冲击下内存和GC更加平稳
● 灵活的TCP 参数配置能力:
○ 合理设置TCP 参数在某些场景下对于性能的提升可以起到显著的效果,例如SO_RCVBUF 和SO_SNDBUF。如果设置不当,对性能的影响是非常大的,SO_RCVBUF 和SO_SNDBUF:通常建议值为128K 或者256K;
○ SO_TCPNODELAY:NAGLE 算法通过将缓冲区内的小封包自动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率。但是对于时延敏感的应用场景需要关闭该优化算法;
● 软中断:
○ 如果Linux 内核版本支持RPS(2.6.35 以上版本),开启RPS 后可以实现软中断,提升网络吞吐量。RPS根据数据包的源地址,目的地址以及目的和源端口,计算出一个hash 值,然后根据这个hash 值来选择软中断运行的cpu,从上层来看,也就是说将每个连接和cpu 绑定,并通过这个hash 值,来均衡软中断在多个cpu 上,提升网络并行处理性能

5、Netty线程与IO模型

传统同步阻塞IO模型
在这里插入图片描述
通常由一个独立的 Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理(缺省情况下所有文件操作都是阻塞的。当一个线程去读取某个缓冲区时,如果缓冲区没有数据,那么这个线程会一致等待下去),处理完成之后,通过输出流返回应答给客户端,线程销毁。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈 1:1 的正比关系,由于线程是 Java 虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
NIO
NIO (New lO)也有人称之为java non-blocking lO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作,NIO将以更加高效的方式进行文件的读写操作。
NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。
● Java NlO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
NIO有三大核心部分: Channel(通道),Buffer(缓冲区),Selector(选择器)
○ Buffer(缓冲区):缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。相比较直接对数组的操作,Buffer APl更加容易操作和管理。
○ Channel(通道):Java NIO的通道类似流,但又有些不同:既可以从通道中读取数据,又可以写数据到通道。但流的(input或output)读写通常是单向的。通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步地读写。
○ Selector(选择器):Selector是一个Java NIO组件,可以能够检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接,提高效率

Netty的线程IO模型是基于Reactor主从多线程模式
主要是Reactor和Handlers
○ Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应,她就像公司的电话接线员,他接听来自客户的电话并将线路转移到适当的联系人
○ Handlers:处理程序执行IO事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员,Reactor通过调度适当的处理程序来相应IO事件,处理程序执行非阻塞操作。

单Reactor模式
在这里插入图片描述
优点:
● 模型简单,没有多线程,进程通信,竞争的问题,全部都在一个线程中完成
缺点:
● 性能问题,只有一个线程,无法完全发挥多核CPU的性能。Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈
● 可靠性问题,线程意外终止,或者进入死循环,会导致整哥系统通信模块不可用,不能接收和处理外部消息,造成节点故障
使用场景: 客户端的数量有限,业务处理非常快速,比如Redis在业务处理事件复杂度O(1)的情况
单Reactor 多线程

在这里插入图片描述
方案说明:
1、Reactor对象通过select监控客户端事件,收到事件后,通过Dispatch进行分发
2、如果建立连接请求,则Acceptor通过accept处理器连接请求,然后创建一个Handler对象处理完成连接后的各种事件
3、如果不是连接请求,则由reactor分发调用连接对应的handler来处理
4、handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务
5、worker线程池会分配独立线程完成真正的业务,并将结果返回给handler
6、handler收到响应后,通过send将结果返回给client

优点:可以充分的利用多核cpu的处理能力
缺点:多线程数据共享和访问比较复杂,reactor处理所有的事件监听,当高并发场景下依然会存在瓶颈

主从Reactor多线程
在这里插入图片描述
方案说明:
1、Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
2、当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
3、subreactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理
4、当有事件发生时,subreactor就会调用对应的handler处理
5、handler通过read读取数据,分发给后面的work线程处理
6、worker线程池分配独立的worker线程进行业务处理,并返回结果
7、handler收到响应的结果后,再通过send将结果返回给client
8、Reactor主线程可以对应多个Reactor子线程,即MainReactor可以关联多个子线程

优点:
父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理
缺点:
编程复杂度较高
使用场景:
这种模型在许多项目中广泛使用,包括Nginx主从Reactor多进程模型,Memcached主从多线程,Netty主从多线程模型的支持

Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor。

在这里插入图片描述

1、Netty抽象出两组线程池BossGroup专门负责接收客户端的连接,WorkGroup专门负责网络的读写
2、BossGroup和WorkGroup类型都是NioEventLoopGroup
3、NioEventLoopGroup相当于一个事件循环组,这个组合含有多个事件循环,每一个事件循环是NioEventLoop
4、NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其上的socket的网络通讯
5、NioEventLoopGroup可以有多个线程,即可以有多个NioEventLoop
6、每个BossNioEventLoop执行的步骤有3步

  1. 轮询accept事件
  2. 处理accept事件,与clent建立连接,生成NioSocketChannel,并将其注册到某个worker的NioEventLoop上的selector
  3. 处理任务队列的任务,即runAllTasks
    7、每个Worker NIOEventLoop循环执行的步骤
  4. 轮询read,write事件
  5. 处理IO事件,即read,write事件,在对应NioSocketChannel处理
    8、每个worker NioEventLoop处理业务时,会使用pipeLine(管道),pipeLine中包含了channel,即通过pipeline可以获取到对应通道,管道中维护了很多的处理器

AIO
NIO2.0 的异步套接字通道是真正的异步非阻塞 I/O,它对应UNIX网络编程中的事件驱动 I/O(AIO),它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,从而简化了 NIO 的编程模型。
IO多路复用模型中,数据到达内核后通知用户线程,用户线程负责从内核空间拷贝数据;
而在异步IO模型中,当用户线程收到通知时,数据已经被操作系统从内核拷贝到用户指定的缓冲区内,用户线程直接使用即可。

在这里插入图片描述
通过java.util.concurrent.Future类来表示异步操作的结果;
在执行异步操作的时候传入一个java.nio.channels;
CompletionHandler接口的实现类作为操作完成的回调。

JDK7已经支持了AIO, netty采用过又放弃了.
· 在LINUX系统上,AIO底层实现仍使用Epoll,没有很好的实现AIO,因此性能上没有明显优势,而且被JDK封装了一层不容易优化
· Netty整体架构是基本reactor模型,而aio是proactor模型,混合在一起会比较混乱
· aio还有个缺点是接收数据需要预先分配缓冲区,而不是NIO那种需要接收时才需要分配缓存,所以对连接数量非常大但流量小的情况,内存浪费很多
· linux上aio不够成熟,处理回调的结果速度跟不到处理需求,供不应求,造成处理速度有瓶颈
6、使用示例
启动一个netty服务端:

public class NettyServer {
 
    public static void startServer(int port){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
 
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
 
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline channelPipeline = socketChannel.pipeline();
                            channelPipeline.addLast(new StringDecoder());
                            channelPipeline.addLast(new StringEncoder());
                            channelPipeline.addLast(new CustomServerHandler());
                        }
                    });
 
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
 
    public static void main(String[] args) {
        startServer(8000);
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值