Netty设计思想之Reactor

作为流量的出入口,高性能的网络I/O服务对于提升系统吞吐量无疑是非常重要的。特别的,对于超大规模并发的网络应用程序来说,可扩展性(Scalability)是支撑高并发的必要条件。 可扩展性意味着随着系统资源的增加,性能的提升是连续的。所以需要尽可能的满足如下两个条件:

事件驱动编程是一种由事件(Event)决定程序流程(Control flow)的编程范式。一般来说,会有一个主循环线程监听事件,当事件触发时指定相应的处理器(Handler)进行处理。这种范式广泛用在前端领域,针对UI事件做一系列不同的处理。

在网络编程领域中,也有一种事件驱动编程的具体实现,称为Reactor模式。Reactor线程监听I/O事件,并在I/O事件准备就绪时,分发至具体的处理器进行处理。

Reactor模式相比传统的多线程IO处理,优势在于:

其高效的原因在于网络I/O是耗时操作,将等待I/O数据就绪的过程抽象成为事件监听并用单独的线程处理,而具体的处理逻辑可以由其他业务线程处理,这样就不会阻塞业务线程从而增大了系统的吞吐量。缺点是提升了编码的复杂度。

Java的NIO网络库封装了的非阻塞I/O的系统调用。Netty则基于Java NIO和Reactor模式设计了一套灵活的、可扩展的网络事件驱动框架。Netty的核心思想就是Reactor模式,并且屏蔽了编码的复杂度,提供了一套简洁的API快速高效的实现网络编程。

本文追本溯源,主要介绍下Netty的设计思想的背景知识。

网络I/O模型

对于网络程序来说,其主要的操作可以简单归纳为如下几步:

由于网络I/O一般需要较长的处理时间,如果让进行系统调用的线程一直阻塞在网络I/O上会显著浪费CPU资源。所以网络I/O系统调用又分为阻塞I/O和非阻塞I/O,这边阻塞的意思是系统内核的数据是否准备好。非阻塞I/O的系统调用如下图所示:

非阻塞I/O和阻塞I/O区别在于系统内核的数据在没有准备好的情况下是否会阻塞调用线程。非阻塞I/O是直接返回结果,而阻塞I/O则会阻塞调用线程。

如果需要管理多个连接的话,每个连接等待I/O事件的过程是一样的,可以用一个线程管理,称为I/O多路复用(I/O multiplexing),一次系统调用可以返回多个准备好的I/O事件。比如linux中的select或者epoll系统调用。I/O多路复用的系统调用如下图所示:

我们可以发现I/O多路复用的系统调用其实就是事件驱动模型中的监听器,可以很自然的实现Reactor模式。

Java NIO

Java NIO(Non-blocking I/O)网络库封装了I/O多路复用的系统调用,提供了I/O多路复用的能力。主要的接口实现有如下:

NIO的非阻塞的意思是业务线程可以以非阻塞的方式读取I/O数据。

Reactor

Reactor模式可以有多种架构,最基础的是单Reactor线程架构,所有的I/O事件都由同一个线程循环监听分发。但为了更好的利用现代多核CPU,可以使用多Reactor线程架构,其架构如下图所示:

主要模块如下:

Netty实现

Netty关于I/O事件处理的设计思想其本质就是Reactor模式,采用的架构也是最先进的多Reactor线程架构。主要的实现类如下:

我们可以看一下用Netty实现的一个简单的EchoServer

// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)
     .channel(NioServerSocketChannel.class)
     .childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(serverHandler);
         }
     });

    // Start the server.
    ChannelFuture f = b.bind(PORT).sync();

    // Wait until the server socket is closed.
    f.channel().closeFuture().sync();
} finally {
    // Shut down all event loops to terminate all threads.
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

总结

Netty作为高性能的基于事件驱动的网络框架,其事件驱动的本质就是多Reactor线程架构,并结合NIO的非阻塞特性实现了高性能的I/O事件处理。这对我们理解Netty的设计思想有非常大的作用。

Powered by Jekyll and Theme by solid