这是NIO系列的第四篇,欢迎继续关注:

  1. 【NIO系列】——之TCP探秘
  2. 【NIO系列】——之IO模型
  3. 【NIO系列】——之Reactor模型

如果你看过前面三篇文章,我们从最低层来分解NIO底层原理和使用方式,帮忙我们理解了NIO是什么,解决了什么问题,以及又有那些不足。

原则上NIO的出现,已经提升和加快了网络IO的处理方式,但它只能帮忙我们解决了IO层次的读写问题,在软件层次上我们需要更好的编程架构模型,来解决扩展性以及高并发的问题。Netty正是用来解决这些问题的,这一篇我们将详细介绍Netty的知识点。

 

一、netty是什么

1、是什么

我们采用官方的话来说:

Netty是一个高性能、异步事件驱动的网络应用框架。 基于Netty,可以快速的开发和部署高性能、 高可用的网络服务端和客户端应用。

简单来说,它是一个网络应用框架,帮你解决面向网络开发中的三个问题:

  • 面向网络IO的读写,如TCP的socket连接

  • 应用层协议编解码,如HTTP协议

  • 高并发架构

那么他有哪些优点?

  • Fast —性能强悍,高并发,低延迟

  • Easy —高扩展,API使用简单,开发门槛低

  • Non-blocking — 无阻塞,支持NIO

 

2、有何不同

对于我们用惯了web容器的人来说,第一个疑惑就是netty究竟能干什么,为何要用它。既然Netty是一个网络应用框架,这些明明tomcat也能帮我们解决了,为何还要用Netty?

netty和tomcat的不同,主要在以下几点:

  • tomcat是一个「HTTP Server」,更准确的说是一个「Servlet/JSP」应用的容器,它主要解决的是 HTTP 协议层面的传输和访问。

  • HTTP是一种应用层协议,应用层的协议除了HTTP以外,还有面向邮局协议的POP和IMAP,以及FTP、LDAP、SSH、TLS/SSL等其他协议。

  • Netty 不仅可以支持HTTP,还可以支持FTP、LDAP、SSH、TLS/SSL等应用层多数的协议,另外还支持自定义的应用层协议,只要你有这方面的需求,它足够灵活。

  • Netty 虽然按归类上算属于OSI的第七层【应用层】,但它的存在是帮你支持第三层【传输层】的问题,比如面向TCP,面向UDP,以及SCTP网络协议的开发,它都能很好的支持。所以他可以称为一个通信组件。

  • 原则上,Tomcat的网络通信组件也可以采用Netty,但servlet3.0之前完全是同步阻塞模型,tomcat要遵循servlet规范所以不能最大发挥NIO的特性,而Netty不用遵循servlet规范,可以最大化发挥NIO的特性,性能更高一些。

 

二、为何要用Netty

Netty的三大特性(fast,easy,no-blocking)可以详细分解为以下特性:

  • 支持NIO,异步编程

  • 性能强悍,高并发,低延迟,更少的资源占用和内存使用率带来更快的性能支撑,NIO领域最强

  • 成熟、稳定,你想到的一切tcp 问题,都已经解决,特别是NIO bug,以及完美解决了。

  • 不仅仅支持http,支持多种应用协议和网络协议,如TCP/UDP/UDT/SCTP/FTP/SMTP等。

  • 功能强大,预制了多种编解码处理器,支持多种主流应用协议

  • API使用简单,开发门槛低

 

1.Fast

为何快,可以参考上篇文章(【NIO系列】——之Reactor模型)介绍的Reactor IO模型。

 

Netty正是基于NIO实现了这种Reactor模型,Boss线程用来专门处理连接的建立,SubReactor专门用来处理IO的读写以及任务的处理。这种线程模型在充分利用CPU性能的情况下支撑大量的并发连接。

 

具体效果如何,我们看下测试数据:

以上是在techempower获取的关于webFrame的纯文本响应测试,可以看到netty在响应速度方面,排名第二,虽然近几年高性能的web框架不断的挑战,netty排名有所下降,但依然还能保持前列位置。

 

以上是在dubbo的官方测试案例中,给定协议序列化TPS对比,可以看到采用netty的dubbo,在tps方面名列前茅。

 

2.更少的内存使用

Netty因为要面对大量的网络数据包处理,所以会有大量的网络对象创建和销毁,对JVM来说有很大的压力。

Netty主要采用两种方案来缓解JVM的压力:

  • ByteBufAllocator 对象池。池化ByteBuf实例以提高性能并最小化内存碎片,后者每次调用时都返回一个新的实例。

  • 零拷贝。支持DirectBuffer的使用,通过JVM的本地调用分配内存,这可避免每次调用本地I / O操作之前(或之后)将缓冲区的内容复制到(或从)中间缓冲区。

以下是twitter对应Netty4的使用对象池应用压测:

 

3.Easy,快速开发

我们知道牵扯到网络上的编程会比较复杂,除了有一堆需要设置的TPC参数以外,还有一堆IO读写的问题要处理,同时为了提高并发能力,还采用多线程,这就又带来了线程安全的问题,往往给开发者带来了很大的挑战和复杂度。

而Netty 正是针对这些难点统一做了简易化的封装,除了让API更加简单易用以外,比如:TCP服务器的配置启动,数据包buffByte的读写等。另外基于事件模式,对网络事件进行串行处理,在保证了高效的同时,又降低了编程的复杂度。

为何串行化执行会提升netty的性能:

  • 串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降。

  • 通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优

  • 减少上下文切换,以及状态数据的同步

 

以下 是ChannelPipeline用来处理网络事件的职责链,负责管理和执行ChannelHandler。网络事件以事件流的形式在ChannelPipeline中流转。支持插拔模式,扩展性也很强。

 

4.可靠稳定

在网络方面,应用常常要面对复杂的网络环境,比如网络环境差,常会出现一些网络闪断,单通以及网络阻塞等等一系列问题。另外作为基础的通信组件,还需要考虑健壮性的问题,因为一旦出现bug,则会导致依赖的大量业务中断。

Netty 在版本迭代中不断的加入一些可靠性特性来满足用户日益高涨的可靠性与健壮性需求。比如NIO的select空转bug,比如TCP的断线重连,keep-alive检测等问题,Netty已经帮你解决了。

特别是Netty在推出4.0之后,已经被各家大厂采纳为通信组件,这是对其可靠性验证,也是社区对其稳定性的认可。以下是在使用netty的一些公司,更多参考netty.Adopters

 

三、如何用

上面介绍了这么多Netty如何厉害,到编码阶段不会出现一堆代码来要处理吧。

我们来一个简单的TCPServer来进行示例.

 

需求:

Client:接收用户输入的内容,并发送给Server,同时收并显示Server返回的内容

Server:监听8088端口,接收并显示Client发送的内容,并给Client相应的回复

 

代码:

      // step 1 设置boss 和 work
      EventLoopGroup bossGroup = new NioEventLoopGroup();
      EventLoopGroup workerGroup = new NioEventLoopGroup();
      try {
          // step 2 服务启动辅助类
          ServerBootstrap b = new ServerBootstrap(); 
          // 设置线程池
          b.group(bossGroup, workerGroup)
              // step3 设置channel
              .channel(NioServerSocketChannel.class) 
              // setp4 设置channel hanlder
              .childHandler(new ChildChannelHandler())
              //设置发送和接受缓冲区大小
              .option(ChannelOption.SO_SNDBUF,256)
              .option(ChannelOption.SO_RCVBUF,256)
              // 小封包自动连接
              .option(ChannelOption.TCP_NODELAY,true)
              .childOption(ChannelOption.SO_KEEPALIVE, true)
              .handler(new LoggingHandler(LogLevel.INFO));
          // step 7 Bind 监听端口.
          ChannelFuture f = b.bind(PORT).sync(); 
          logger.info("tcp server start success... ");
          f.channel().closeFuture().sync();
      } finally {
      }

ChildChannelHander 扩展Pipeline的执行策略:

protected void initChannel(SocketChannel channel) throws Exception {
      ChannelPipeline pipeline = channel.pipeline();
      pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
      pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
      pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
      pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
      pipeline.addLast(new TcpHelloServerHandler());
​
  }

 

通过以上代码,我们大概需要通过7个步骤,就能创建一个TCP的Server服务器了,看似7步较多,其实看代码量来说,已经很少了。

通过以上的执行流程图,我们可以看到:

  • ServerBootstrap是服务启动(引导)辅助类,采用无参构造器(Builder模式)提供一系统方法用于设置启动相关的参数

  • EventLoopGroup:Netty的Reactor线程池,负责维护一组EventLoop的调度工作,通常是bossEventLoop用来维护连接,subEventLoop来维护所有已注册的Channel的I/O操作处理用户自定义的Task和定时任务的Task

  • Channel:对Java原生的NIO类库进行了封装对一般用户而言,不需要关心底层实现细节和工作原理,只需要指定具体使用哪种Channel,用以连接IO设备(socket)的纽带,提供与IO设备异步I/O操作的支持(如读、写、连接和绑定)

  • ChildChannelHandler,用来配置ChannelPipeline的执行策略。用来扩展的关键接口,用户可以完成大多数的功能定制,如消息编解码、心跳、安全认证、TSL/SSL认证、流量控制等。

 

四、最后

以上我们通过Netty 是什么、怎么用、为何用三个方面阐述了Netty的优势以及基本的介绍,想必能够对Netty做一个基本的了解。后续我们将对Netty高性能架构,以及多线程高效编程方面,结合源码,会有更多的剖析。我们不仅仅关注源码是如何实现了,还会结合整个架构,来分析设计理念,以及探讨鲁棒性很强的代码的编写方式。若有兴趣,欢迎持续关注。