大白话聊聊Netty

303次阅读  |  发布于4月以前

阿里妹导读

随着云计算、大数据和物联网的发展,Netty的潜力将进一步释放。作者通过本文跟大家聊聊Netty是什么?为什么选Netty?以及它的运行原理。

Netty是什么

官方介绍:> Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers & clients.

翻译:Netty是一个异步事件驱动网络应用程序框架用于快速开发**可维护高性能**协议服务器和客户端。

从这里可以看出Netty本质是网络应用程序框架,实现方式是异步和事件驱动,特点是高性能、易开发、可维护性高。

为什么选Netty

几乎所有我们叫得上名字的框架,底层用的都是Netty,包括RocketMQ、Elasticsearch、gRPC、Apache Dubbo、Spring5、HSF、 Zookeeper、Spark、Hadoop,这足以说明Netty一定是有它巨大的优势。优势总结如下:

接下来详细介绍一下这些优势在Netty中具体体现。

开发门槛低

BIO vs NIO vs AIO

BIO: 同步阻塞式IO 打个比喻您打了专车,在您没有到之前司机就在出发地等您上车。您上车之后司机专门送您到目的地。在这个例子中您扮演着IO中的网络事件,司机扮演着处理网络事件的线程。整个过程中您如果没有任何事件发生司机一直都在等待这就是同步阻塞。

NIO:同步非阻塞IO 银行柜员在等待人办理银行业务,人们去银行后首先要到取号机上取号然后等待对应的柜台叫号。在这个例子中银行柜员扮演着selector,办理银行业务的人扮演者网络事件,而取号机扮演者register的作用,银行柜台就是channel。整个过程中当没有人来办业务时,柜员是可以去做其他事情这就是非阻塞。

AIO:异步非阻塞IO 您点外卖后就去忙其他的事情了,等骑手把外卖送达后打电话告诉您外卖放外卖柜子里了;您闲下来的时候去取外卖。在这个例子里您扮演着处理网络事件线的程,外卖是网络事件;在这个过程中您可以在外卖还没有送来的时候做些其他的事情,等外卖送达后只是向您发送了一个外卖送达的事件。这就是异步和非阻塞。

通过网络服务端代码的编写来让我们直观感受一下他们的区别:

BIOServer

public class Server {
  //定义一个循环接收客户端的Socket连接请求。初始化一个线程池对象
  private static ExecutorService poolHandler = new ThreadPoolExecutor(5, 5,
                120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
    public static void main(String[] args) {
        try {
            // 注册端口
            ServerSocket ss = new ServerSocket(9999);
            while (true) {
                Socket socket = ss.accept();
                // 把Socket封装成一个任务对象交给线程池处理
                Runnable target = new ServerRunnable(socket);
                poolHandler.execute(target);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static class ServerRunnable implements Runnable {
      private Socket socket;
      public ServerRunnable(Socket socket) {
          this.socket = socket;
      }
      @Override
      public void run() {
          // 处理接收的客户端Socket通信需求
          try {
              InputStream is = socket.getInputStream();
              BufferedReader br = new BufferedReader(new InputStreamReader(is));
              String msg;
              while ((msg = br.readLine()) != null) {

                //处理数据的粘包拆包
                //对完整的数据包进行解码操作
                //得到客户端消息
                //触发各种统计类事件如心跳检测 信息统计 
                //处理客户端的消息
                //得到响应消息
                //对响应消息进行编码
              }
          } catch (IOException e) {
              e.printStackTrace();
            //处理网络断开事件
            //处理其他异常事件
          }
      }
  }
}

NIOServer

public class Server {
    public static void main(String[] args) throws IOException {
        //获取ServerSocketChannel
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //设置非阻塞模式
        ssChannel.configureBlocking(false);
        //绑定端口
        ssChannel.bind(new InetSocketAddress(9999));
        //获取选择器
        Selector selector = Selector.open();
        //将ServerSocketChannel注册到选择器上,并且监听建立连接事件
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 使用Selector选择器轮询已经就绪好的事件
        while (selector.select() > 0) {
            // 获取选择器就绪事件
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            //遍历事件
            while (it.hasNext()) {
                SelectionKey sk = it.next();
                //判断事件类型
                if (sk.isAcceptable()) {
                    // 获取客户端channel
                    SocketChannel channel = ssChannel.accept();
                    //切换非阻塞模式
                    channel.configureBlocking(false);
                    //将该channel注册到选择器上
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (sk.isReadable()) {
                    //获取channel
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //读取网络数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = sChannel.read(buf)) > 0) {
                        //处理数据的粘包拆包
                        //对完整的数据包进行解码操作
                        //得到客户端消息
                        //触发各种统计类事件如心跳检测 信息统计 
                    }
                }else if(sk.isWritable()){
                        //得到响应消息
                        //对响应消息进行编码
                }
                // 15.取消选择键SelectionKey
                it.remove();
            }
        }
    }
}

用Netty进行网络编程时代码是这样的:

NettyServer

public class NettyServer {
    public static void main(String[] args) throws Exception{
        //设置接受网络连接线程池
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //设置处理网络除连接外所有事件线程的线程池
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        serverBootstrap.group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)//设置Channel类型
                .option(ChannelOption.SO_BACKLOG,1024)
                .handler(new ChannelInitializer<ServerSocketChannel>() {
                    @Override
                    protected void initChannel(ServerSocketChannel ch) throws Exception {
                      //设置处理网络连接的Handler  
                      ch.pipeline().addLast("serverBindHandler",
                         new NettyBindHandler(NettyTcpServer.this,serverStreamLifecycleListeners));
                    }
                })
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast("protocolHandler", new NettyProtocolHandler())//设置编解码器
                                .addLast("serverIdleHandler",
                                        new IdleStateHandler(0, 0, serverIdleTimeInSeconds))//设置心跳检测
                                .addLast("serverHandler",new NettyServerStreamHandler(NettyTcpServer.this, false,
                                        serverStreamLifecycleListeners, 
                                         serverStreamMessageListeners));//设置业务处理逻辑
                    }
                });

        ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
        channelFuture.channel().closeFuture().sync();
    }
}

BIO和NIO编程在网络事件发生后都需要进行处理数据的粘包拆包、对完整的数据包进行解码、触发各种统计类事件、对响应消息进行编码、监听各种网络异常、需要对底层网络和通信协议有一定的了解。

用Netty编程时只需要设置Handler就能快速的进行业务开发而不用关心数据的读取及网络事件的分发处理.让开发者从底层网络通信中解放出来。从这些对比中我们可以看出Netty开发网络程序要求低,使用者无需太多关心和业务无关的信息。

定制能力强,通过ChannelHandler灵活扩展

多数情况下我们自定义的Handler会有多个,那么他们是怎么进行协调合作的呢?接下一起探索handler之间的协调合作。

通过上图我们可以知道我们定义的handler在Netty里是通过双向链表进行关联的。

介绍一下读网络事件发生后Handler事件流:

当服务端有数据需要写入时又会发生什么呢?

我们可以看出Netty通过控制InboundHandler节点的调用来决定读事件响应链路;通过控制OutboundHandler节点的调用来决定写事件调用链路。

Handler强大

Netty通过内置多种Handler让你在不了解底层网络、通信协议、编解码的背景下也可进行网络应用程序。它都内置有哪些Handler呢?

编解码

当你通过Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码,从字节转换为另一种格式(比如java对象)。如果是出站消息,它会被编码成字节。

TCP粘包拆包

大多数基于Netty通信底层都会使用TCP进行的通信。TCP在发送数据流的时一定会把整条数据流单独发送吗?答案是否定的。TCP在发送数据包的时会存在拆包和粘包问题。什么是TCP的粘包和拆包呢?

由于篇幅有限关于TCP拆包和粘包只是大致说明了一些。

编解码Handler

Netty提供了三种解码器来解决TCP的拆包和粘包,他们分别是:

此外Netty还提供了N:

除了以上介绍的编解码Netty内部还有很多内置编解码器,当您使用Netty开发时如果有用到编解码的时候可以首先查询一下Netty内部是否有实现。

支持多种主流协议

从Netty源码包上可以看出Netty基本上覆盖了主流协议的编解码实现,如HTTP、Protobuf、WebSocket、二进制等主流协议。

idle(心跳检测)

当需要监听网络连接是否长时间没有数据交换(如发送心跳包、关闭连接等)就可以使用内置的IdleStateHandler。IdleStateHandler接收三个参数:

当达到设定的空闲时间阈值时,IdleStateHandler会触发对应的IdleState事件,这些事件包括READ_IDLE、WRITE_IDLE和ALL_IDLE。你可以通过实现ChannelInboundHandler的channelIdle方法来监听这些事件,并在事件发生时执行相应的操作。

高性能

一次网络通信都发生了什么

  1. 客户端确定需要发送的数据;
  2. 数据从程序到系统然后通过网卡发送;
  3. 服务端收到读事件后把数据从系统读取到应用中;
  4. 应用处理客户端信息;

Netty怎么提高通信性能

reactor单线程模型: 有一个线程负责处理所有的网络事件。

Reactor多线程模型:有一个线程单独处理建立网络事件,另外一个线程负责处理其他的网络事件。

主次Reactor多线程模型:有一个线程单独处理建立网络事件,把建立网络连接放到线程池中的某一个线程中,这个线程负责大量网络连接的其他请求.

从reactor线程模型上我们可以看出主次Reactor多线程模型可以快速对大量的网络事件进行响应,因此也会缩短网络事件处理时间.

我们可以看到Netty针对网络传输的各个节点都做到了尽可能的缩短时间,这也是Netty高性能的原因所在。

Netty运行原理

通过以上对Netty是什么,优势有哪些已经有了初步了解。那么它内部是怎么工作的呢?接下来让我们一起看一下它内部的原理。

整体结构

上面这张图就是在官网首页的架构图,我们从上到下分析一下。

以上可看出Netty的功能、协议、传输方式都比较全,比较强大。

逻辑架构

从图中可以Netty通过网络通信层、事件调度层、服务编排层协调合作来运行程序

网络通信层

网络通信层的职责是执行网络 I/O 的操作。当网络数据读取到内核缓冲区后,会触发各种网络事件。这些网络事件会分发给事件调度层进行处理。接下来分别看一下Netty服务端和客户端在网络通信层是怎么运行的:

事件调度层

事件调度层的职责是通过 Reactor 线程模型对各类事件进行聚合处理,通过 Selector 主循环线程集成多种事件(I/O 事件,信号事件,定时事件等),实际的业务处理逻辑是交由服务编排层中相关的 Handler 完成。事件调度层主要由EventLoopGroup和EventLoop构成。

服务编排层

服务编排层的职责是通过组装各类handler来实现网络数据流的处理。它是 Netty 的核心处理链,用以实现网络事件的动态编排和有序传播。服务编排层的核心组件包括 ChannelHandler、ChannelHandlerContext、ChannelPipeline

他们之间的关系如下图所示:

运行流程

我们已经从宏观上了解了Netty,接下来我们从服务端的视角简要的看一下Netty整个的运行流程。

  1. 服务端启动的时把ServerSocketChannel注册到boss EventLoopGroup中某一个EventLoop上,暂时把这个EventLoop叫做server EventLoop;
  2. 当 serverEventLoop中监听到有建立网络连接的事件后会把底层的SocketChannel和serverSocketChannel封装成为NioSocketChannel;
  3. 开始把自定义的ChannelHandler加载到NioSocketChannel 里的pipeline中,然后把该NioSocketChannel注册到worker EventLoopGroup中某一个EventLoop上,暂时把这个EventLoop叫做worker EventLoop;
  4. worker EventLoop开始监听NioSocketChannel上所有网络事件;
  5. 当有读事件后就会调用pipeline中第一个InboundHandler的channelRead方法进行处理;

结语

Netty以其高效、灵活和强大的特性,已经成为了Java平台上构建高性能网络应用的首选框架。Netty 的设计哲学不仅简化了复杂网络编程的挑战,还为企业级应用提供了坚实的基础。

随着云计算、大数据和物联网的发展,Netty的潜力将进一步释放。未来,我们可以预见Netty在微服务架构、实时数据传输和边缘计算等领域发挥更大的作用。

对于开发者而言,掌握Netty的精髓意味着拥有了一把解锁高并发、低延迟网络解决方案的金钥匙。

因此,无论你是初学者还是经验丰富的工程师,投入时间学习和精通Netty都是一项值得的投资。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8