Netty系列文章之Netty线程模型

8次阅读

共计 3180 个字符,预计需要花费 8 分钟才能阅读完成。

前言

我们在使用 Netty 进行服务端开发的时候,一般来说会定义两个 NioEventLoopGroup 线程池,一个 ”bossGroup” 线程池去负责处理客户端连接,一个 ”workGroup” 线程池去负责处理读写操作。那么,我们为什么要这么做呢?这样做的好处是什么呢?能不能只使用一个 NioEventLoopGroup 呢?这就是我们今天要讨论的主题——Netty 的线程模型

Reactor 线程模型

实际上 Netty 线程模型就是 Reactor 模式的一个实现,而 Reactor 模式又是什么呢?

Reactor 模式是基于事件驱动开发的,核心组成部分包括 Reactor 和线程池,其中 Reactor 负责监听和分配事件,线程池负责处理事件,而根据 Reactor 的数量和线程池的数量,又将 Reactor 分为三种模型:

  • 单线程模型 (单 Reactor 单线程)
  • 多线程模型 (单 Reactor 多线程)
  • 主从多线程模型 (多 Reactor 多线程)

单线程模型

图片摘自:http://gee.cs.oswego.edu/dl/c…

  • Reactor 内部通过 selector 监控连接事件,收到事件后通过dispatch 进行分发,如果是连接建立的事件,则由 Acceptor 处理,Acceptor通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件, 如果是读写事件,直接调用连接对应的 Handler 来处理
  • Handler 完成 read->(decode->compute->encode)->send 的业务流程
  • 这种模型好处是简单,坏处却很明显,当某个 Handler 阻塞时,会导致其他客户端的 handler 和 accpetor 都得不到执行,无法做到高性能,只适用于业务处理非常快速的场景

多线程模型

图片摘自:http://gee.cs.oswego.edu/dl/c…

  • 主线程中,Reactor 对象通过 selector 监控连接事件, 收到事件后通过 dispatch 进行分发,如果是连接建立事件,则由 Acceptor 处理,Acceptor 通过 accept 接收连接,并创建一个 Handler 来处理后续事件,而 Handler 只负责响应事件,不进行业务操作,也就是只进行 read 读取数据和 write 写出数据,业务处理交给一个线程池进行处理
  • 线程池分配一个线程完成真正的业务处理,然后将响应结果交给主进程的 Handler 处理,Handler 将结果 send 给 client (下面是核心代码)

单 Reactor 承当所有事件的监听和响应, 而当我们的服务端遇到大量的客户端同时进行连接,或者在请求连接时执行一些耗时操作,比如身份认证,权限检查等,这种瞬时的高并发就容易成为性能瓶颈

主从多线程模型(最流行)

图片摘自:http://gee.cs.oswego.edu/dl/c…

  • 存在多个 Reactor,每个 Reactor 都有自己的 selector 选择器,线程和 dispatch
  • 主线程中的 mainReactor 通过自己的 selector 监控连接建立事件,收到事件后通过 Accpetor 接收,将新的连接分配给某个子线程
  • 子线程中的 subReactor 将 mainReactor 分配的连接加入连接队列中通过自己的 selector 进行监听,并创建一个 Handler 用于处理后续事件
  • Handler 完成 read-> 业务处理 ->send 的完整业务流程

Netty 中的线程模型与 Reactor 的联系

Netty 主要靠 NioEventLoopGroup 线程池来实现具体的线程模型的

单线程模型

单线程模型就是只指定一个线程执行客户端连接和读写操作,也就是在一个 Reactor 中完成,对应在 Netty 中的实现就是将 NioEventLoopGroup 线程数设置为 1,核心代码是:

 NioEventLoopGroup group = new NioEventLoopGroup(1);
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ServerHandlerInitializer());

它的工作流程大致如下:

上述单线程模型就对应了 Reactor 的单线程模型

多线程模型

多线程模型就是在一个单 Reactor 中进行客户端连接处理,然后业务处理交给线程池,核心代码如下:

NioEventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ServerHandlerInitializer());

走进 group 方法可以发现我们平时设置的 bossGroup 和 workerGroup 就是使用了同一个 group

@Override
public ServerBootstrap group(EventLoopGroup group) {return group(group, group);
}

工作流程如下:

主从多线程模型 (最常使用)

主从多线程模型是有多个 Reactor,也就是存在多个 selector,所以我们定义一个 bossGroup 和一个 workGroup,核心代码如下:

NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ServerHandlerInitializer());

工作流程如下:

注意:其实在 Netty 中,bossGroup 线程池最终还是只会随机选择一个线程用于处理客户端连接,与此同时,NioServerSocetChannel 绑定到 bossGroup 的线程中,NioSocketChannel 绑定到 workGroup 的线程中

小结

以上总结了 Reactor 的三种模型以及 Netty 中的对应实现,在 Netty 中,我们使用的最多的还是主从多线程模型。关于 Reactor 的学习,最权威的资料应该是 Doug Lea 大神的 Scalable IO in Java,有兴趣的同学可以看看

参考资料

  • http://gee.cs.oswego.edu/dl/c…
  • https://time.geekbang.org/col…
  • https://segmentfault.com/a/11…
  • https://www.infoq.cn/article/…
正文完
 0