【原创】Netty 内核

作者:星巴刻

       

一、Netty 内核组

        Netty 运行时包含了多个内核。在服务端程序中,需要分别创建 parent 和 child 两种内核: 1 个 parent 内核和 16 个 child 内核( 8 核 CPU系统下的默认数)。为简便起见,以下简单以 16 来代替实际的 child 内核数。因此,用如下大图来概括 Netty 内核组:中间是 17 个内核组成的内核组,左边是操作系统,右边是应用程序:

Netty 内核组

        每一个 Channel 都必须属于且仅属于某一个内核。系统中代表服务端侦听端口的 NioServerSocketChannel 属于 parent 。代表不同客户端连接的 NioSocketChannel 属于 16 个child 中的一个。当 Channel 创建后,Netty 需要安排一个内核来负责它,这个过程称为 register (注册)。注册过程就是调用 NioEventLoopGroup.next() 方法返回一个 NioEventLoop,调用 NioEventLoop 的 register(channel)  方法完成。

二、端口侦听内核

端口侦听内核图

1、初始化

        端口侦听内核的创建与初始化属于服务端整体初始化的一部分。这一部分可通过 ServerBootstrap 完成。对照上图,可以清晰地看出,初始化工作主要要构建如下对象,并把它们「串在一起」:

1. 创建一个 NioServerSocketChannel  ,用于表示用于接收接受客户端连接的端口
2. 打开一个 Selector,用于发现 NioServerSocketChannel 上有新的客户端连接
3. 创建一个 NioEventLoop 线程以及内部队列,让执行端口绑定、客户端连接到来等程序在这个线程执行
4. 创建一个  ServerBootstrapAcceptor  ,接受新来的客户端连接,并初始化后续处理它的对象。

        应用程序创建 NioEventLoopGroup 时,NioEventLoopGroup 内部将根据指定的参数自动创建 NioEventLoop 实例。NioEventLoop 随之把其内部线程、队列创建起来,并把打开一个新的 Selector 选择器。这样,内核线程、内部队列、Selector 选择器就天然地属于这个 NioEventLoop 了。

        应用程序调用 ServerBootstrap.bind() 方法时,ServerBootstrap 将创建 NioServerSocketChannel 对象及其  ServerBootstrapAcceptor 实例放入 NioServerSocketChannel 的 pipeline 中去。随后将创建的 NioServerSocketChannel 注册到 NioEventLoop 中,由 NioEventLoop 在其内部线程中执行将 NioServerSocketChannel 注册到 Selector 选择器的代码:

NioServerSocketChannel 与 Selector 的关联

        至此,端口侦听内核的所有对象都创建完毕,内部对象已经关联起来只差把内核绑定到操作系统中。

2、注册 OP_ACCEPT

        侦听内核为了能够感知有新的客户端到来,必须注册对 OP_ACCEPT 事件的兴趣,这个工作在上面的初始化中完成,这里单独列出来说明。在 NioServerSocketChannel 注册到内核工作完成后, DefaultPipeline.channelActive 方法除了通知 channel 已经打开,紧接着马上调用 channel.read() ,在 Netty 中,channel.read 不是真正要去从系统缓冲区读取信息,而是表示要注册一个读取事件。因此,channel.read() 的调用通过 pipeline 后,最终将调用到 channel 自身的 doBeginRead() 方法,将  selectionKey 的 interestOps 属性增加 OP_ACCEPT 值。相关源代码如下:

DefaultChannelPipeline.HeadContext 主动调用 channel.read()
注册 OP_ACCEPT

3、端口绑定

        应用程序调用 ServerBootstrap.bind() 完成相关的初始化工作后,最后就是将整个内核和操作系统关联起来,也就是真正将 NioServerSocketChannel 绑定到指定的端口上。类似 register,将 NioServerSocketChannel bind 到操作系统上,需要调用 Java Nio 的 ServerSocketChannel 的 bind 方法,这个工作在 Netty 内核下,也将在 NioEventLoop 内部线程来实际执行:

在这里,将 bind 工作转交给 NioEventLoop 的内部线程
调用 Java Nio 的类完成端口绑定

        至此,Netty 已经可以接收客户端连接了。

4、接受连接

        对照《端口侦听内核图》,当有新的客户端连接到来时,NioEventLoop 调用选择器选择当前发生的 I/O 事件时,将得到含有 OP_ACCEPT 事件的 selectionKey。NioEventLoop 的 processSelectedKey 方法一一处理这些 I/O 事件,对于 OP_ACCEPT 事件, NioServerSocketChannel 的 doReadMessages 方法将封装出一个 NioSocketChannel:

        这个 NioSocketChannel 对象将被之前初始化时创建到 pipeline 中的 ServerBootstrapAcceptor 获得,在里面将新的客户端连接安排到某个 child 内核实例中:

        至此,就可以进行客户端连接的读写了。

三、连接的读写

        客户端和服务端之间连接上的信息读写以及处理,在 Netty 中使用如下统一的内核来完成。在服务端程序中,由于多了一个侦听端口的组,此内核在服务端中归为 child 组;但在客户端中,就只有这个组,此时它归为客户端中的 parent 组。这样的分组稍微拗口,我们完全可以简单地直接称为它「端口读写内核」,以区别服务端程序特有的「端口侦听内核」。

端口读写内核图

        如前所述,一般地,服务端程序中会有 16 个连接读写内核,典型的客户端通常只有 1 个。这主要是因为,客户端往往只和服务端建立 1 个或少数几个连接,而服务端则要同时维护数量庞大的客户端连接。好在,1 个或多个,对 Netty 来说其内核架构是统一的,我们可以统一来理解,不用分开看。

        端口读写内核中,一个内核负责多个 NioSocketChannel 连接,这些连接注册到选择器中,以便通过选择器发现该 channel 的 I/O事件,其中 OP_READ 是最关键的 I/O 事件。NioEventLoop 的内部线程调用选择器进行选择,当注册到选择器中的 NioSocketChannel 有新的 OP_READ 等 I/O 事件时,完成底层操作后(比如将信息读入 ByteBuf),NioEventLoop 将调用和该 channel 一一对应的 ChannelPipeline 中的 ChannelInboundHandler 的 channelRead 等方法进行处理,最终使得最右边的应用程序逻辑得到执行。

        每个 NioSocketChannel 都有自己的 ChannelPipeline 对象。对照上面的内核图中 pipeline 的部分,左边是它的 head,右边是它的 tail。每个 ChannelPipeline 可以简单地看做有 2 行,上面行是处理来自内核发出的事件(简称处理 InboundEvent ),底下行处理来自应用程序发出的动作(简称处理 OutboundEvent )。每一行都可以包含不限制个数的 ChannelHandler 模块。

      Netty 内核是在其内核线程中调用 ChannelPipleline 的方法提交处理 InboundEvent 或 OutboundEvent,但并不意味着 ChannelPipeline 中的 ChannelHandler 的 channelRead 等方法一定是在 Netty 的内核线程中执行的。这主要 bootstrap 中,ChannelInitializer.initChannel 方法中是如何调用 pipeline 的,以调用 addLast 为例子,如果调用的是 addLast(EventExecutorGroup, ChannelHandler...handlers),即在第 1 个参数指定了一个 EventExecutorGroup,那么 handlers 中的方法将由这个 EventExecutorGroup 提供的一个 EventExecutor 执行,并且之后这个 handlers 的执行一直都由这个 EventExecutor 执行,不再在 Netty 的内核线程了!这个特性的使用需要精心去了解、适时使用,它对性能有重大帮助或影响。

四、总结

        虽然 Netty 为网络开发提供了高性能的能力,以及简便的开发框架和各种开箱套件。但要写出良好的 Netty 程序,花点时间看下 Netty 的要点还是值得的。如果只是模模糊糊地使用 Netty 也总能被坑。

        本文是市面上 第一个提出 Netty 内核 概念的文章,希望借此有助于理解 Netty 的核心要点。Netty 的要点在其内核体现了 Reactor 编码架构,并根据实际需要进行了扩展。

        以服务端程序为例,一个应用程序会包含一个 端口侦听内核 以及 16 个连接读写内核(8 核 CPU下的默认设置)。这些内核具有同构性。内核包含一个内部线程和队列。代表服务端端口的 NioServerSocketChannel 或者代表客户端的 NioSocketChannel 必须选择注册到某一个内核中,内核通过选择器发现新的 I/O 事件的到来,进行初步加工,然后交给各自 channel 对应的生产线去 pipeline 处理。应用程序也会主动发起一些工作,这些被称为 Outbound 事件,比如往 channel 写入信息或者关闭 channel。这些 Outbound 事件也会由经过 pipleline ,最终再进入内核处理。

        ChannelPipeline 处理 Inbound 由内核发起,处理 Outbound 事件由应用程序发起,但是 pipeline 中的每个处理器在处理事件时,都可以事先通过 EventExecutorGroup 获得的一个 EventExecutor 执行。对于那些耗时的工作,比如调用数据库、远程服务的处理模块,设置一个独立于内核线程的 EventExecutorGroup 是有绝对必要的。

2017-11-22

原文地址:http://www.jianshu.com/p/045e01dc18e2

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容