netty begin

what

Netty 提供 异步的、事件驱动 的网络应用程序框架和工具。

作为一个异步框架,Netty 的所有 IO 操作都是异步非阻塞的通过 Future-Listener 机制,用户可以方便地 主动获取 或者 通过通知机制 获得 IO 操作结果

netty vs nio

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

功能强大,预置了 多种编解码功能,支持多种主流协议

定制能力强,可以通过 ChannelHandler 对通信框架进行灵活扩展

性能高,与其他业界主流的 NIO 框架对比,Netty 的综合性能最优。

成熟、稳定,Netty 修复了已经发现的所有 JDK NIO 中的 BUG,业务开发人员不需要再为 NIO 的 BUG 而烦恼。

社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复。

demo

channel -> pipeline -> handlers
每个 Channel 内部都有一个 pipeline,pipeline 由多个 handler 组成, handler 之间的顺序是很重要的, 因为 IO 事件将按照顺序顺次经过 pipeline 上的 handler

ChannelFactory.newChannel() 方法的调用时机

对于 NioSocketChannel, 由于它充当客户端的功能, 它的创建时机在 connect(…) 的时候;
对于 NioServerSocketChannel 来说, 它充当服务端功能, 它的创建时机在绑定端口 bind(…) 的时候.

Promise

Promise 实例内部是一个任务, 任务的执行往往是异步的, 通常是一个线程池来处理任务.

Promise 提供的 setSuccess(V result) 或 setFailure(Throwable t) 将来会被某个执行任务的线程在执行完成以后调用,
同时那个线程在调用 setSuccess(result) 或 setFailure(t) 后会回调 listeners 的回调函数 (当然, 回调的具体内容不一定要由执行任务的线程自己来执行, 它可以创建新的线程来执行, 也可以将回调任务提交到某个线程池来执行).

而且, 一旦 setSuccess(…) 或 setFailure(…) 后, 那些 await() 或 sync() 的线程就会从等待中返回.

所以这里就有两种编程方式,
一种是用 await(), 等 await() 方法返回后, 得到 promise 的执行结果, 然后处理它;
另一种就是提供 Listener 实例, 我们不太关心任务什么时候会执行完, 只要它执行完了以后会去执行 listener 中的处理方法就行.

在 Netty 中,IO 事件被分为 Inbound 事件和 Outbound 事件

Outbound 的 out 指的是 出去, 有哪些 IO 事件属于此类呢?比如 connect,write,flush 这些 IO 操作是往外部方向进行的, 它们就属于 Outbound 事件.

其他的, 诸如 accept,read 这种就属于 Inbound 事件.

ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
ch.pipeline().addLast("decoder", new StringDecoder());// Inbound
ch.pipeline().addLast("encoder", new StringEncoder());// Outbound
ch.pipeline().addLast(new EchoServerHandler());// Inbound

对于 Inbound 操作, 按照添加顺序执行每个 Inbound 类型的 handler; 而对于 Outbound 操作, 是反着来的, 从后往前, 顺次执行 Outbound 类型的 handler.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNlJamUI-1596045520578)(…/…/assets/2019-05-13-14-15-55.png)]

一个 Channel 关联一个 pipeline

pipeline 现在的样子, head + channelInitializer + tail

线程池

首先, 我们说的 Netty 的 线程池, 指的就是 NioEventLoopGroup 的实例;

线程池中的 单个线程, 指的是 NioEventLoop 的实例.

Channel 与 Buffer

对于 Buffer 来说, 另一个常见的操作中就是, 我们要将来自 Channel 的数据填充到 Buffer 中, 在系统层面上, 这个操作我们称为读操作, 因为数据是从外部 (文件或网络等) 读到内存中.

int num = channel.read(buf);

上述方法会返回从 Channel 中读入到 Buffer 的数据大小

从 Buffer 中读出来写入 Channel
通过 SocketChannel 将数据写入网络发送到远程机器等. 对应的, 这种操作, 我们称之为写操作.

int num = channel.write(buf);

Channel

Channel 经常翻译为通道, 类似 IO 中的流, 用于读取和写入. 它与前面介绍的 Buffer 打交道,
读操作的时候将 Channel 中的数据填充到 Buffer 中,
而写操作时将 Buffer 中的数据写入到 Channel 中.

Selector

NIO 三大组件就剩 Selector 了,

Selector 建立在非阻塞的基础之上,

大家经常听到的 多路复用 在 Java 世界中指的就是它,用于实现一个线程管理多个 Channel.

将 Channel 注册到 Selector 上. 前面我们说了, Selector 建立在非阻塞模式之上, 所以注册到 Selector 的 Channel 必须要支持非阻塞模式

// 将通道设置为非阻塞模式, 因为默认都是阻塞模式的
channel.configureBlocking(false);
// 注册
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

  1. 多进程处理
    对于应用服务器, 一个主要规律就是, CPU 的处理速度是要远远快于 IO 速度的, 如果 CPU 为了 IO 操作 (例如从 Socket 读取一段数据) 而阻塞显然是不划算的.
    好一点的方法是分为多进程或者线程去进行处理, 但是这样会带来一些进程切换的开销

  2. 回调
    这时先驱们找到了事件驱动, 或者叫回调的方式, 来完成这件事情. 这种方式就是, 应用业务向一个中间人注册一个回调 (event handler), 当 IO 就绪后, 就这个中间人产生一个事件, 并通知此 handler 进行处理.

  3. Reactor
    在前面事件驱动的例子里有个问题:
    我们如何知道 IO 就绪这个事件, 谁来充当这个中间人?
    Reactor 模式的答案是: 由一个不断等待和循环的单独进程 (线程) 来做这件事, 它接受所有 handler 的注册, 并负责先操作系统查询 IO 是否就绪, 在就绪后就调用指定 handler 进行处理, 这个角色的名字就叫做 Reactor.

NIO 中 Reactor 的核心是 Selector

public void run() {
    try {
        while(!Thread.interrupted()) {
            selector.select();
            Set selected = selector.selectedKeys();
            Iterator it = selected.iterator();
            while(it.hasNext())
            dispatch((SelectionKey)(it.next()));
            selected.clear();
        }
    } catch(IOException ex) {
    /* ... */
    }
}

Selector.select()

NIO 的 Selector 有三个 select() 方法, 它们的区别如下:

select() 阻塞直到有一个感兴趣的 IO 事件就绪

select(long timeout)select() 类似, 但阻塞的最长时间为给定的 timeout

selectNow() 不会阻塞, 直接返回而不管是否有 IO 事件就绪

wakeUp() 方法, 其功能是唤醒一个阻塞在 select() 上的线程, 使其继续运行

本来 select 操作的代码不会这么复杂,
主要是由于 JDK BUG 导致 select() 方法并不阻塞而直接返回且返回值为 0, 从而出现空轮询使 CPU 完全耗尽.

Netty 解决的办法是: 对 select 返回 0 的操作计数, 如果次数大于阈值 SELECTOR_AUTO_REBUILD_THRESHOLD 就新建一个 selector, 将注册到老的 selector 上的 channel 重新注册到新的 selector 上.

阈值 SELECTOR_AUTO_REBUILD_THRESHOLD 可由用户使用系统变量 io.netty.selectorAutoRebuildThreshold 配置, 默认为 512. 这里注意 for()循环中大量使用了 break, 含有 break 的部分才是关键操作, 其他部分 (其实就只有一处) 是为了解决 JDK BUG.

https://www.jianshu.com/p/a06da3256f0c

NioEventLoop 和 NioEventLoopGroup 继承结构


NioEventLoop
--> SingleThreadEventLoop
--> SingleThreadEventExecutor
--> AbstractScheduledEventExecutor
--> AbstractEventExecutor
--> AbstractExecutorService

NioEventLoopGroup
--> MultithreadEventLoopGroup
--> MultithreadEventExecutorGroup
--> AbstractEventExecutorGroup

Netty 使用了相对时间调度, 时间起点为 ScheduledFutureTask 类第一次被类加载器加载的时间

当调用 shutdownGracefully() 时, 线程状态改变为 ST_SHUTTING_DOWN; 调用 shutdown() 时, 线程状态改变为 ST_SHUTDOWN

Future 接口中的方法都是 getter 方法而没有 setter 方法, 也就是说这样实现的 Future 子类的状态是不可变的, 如果我们想要变化, 那该怎么办呢?Netty 提供的解决方法是: 使用可写的 Future 即 Promise

Future 和 Promise

AbstractFuture
<--
CompleteFuture
<--
CompleteChannelFuture
<--
Succeeded/FailedChannelFuture

DefaultPromise
<--
DefaultChannelPromise

由于 CompleteFuture 表示一个已完成的异步操作, 所以可推知 sync() 和 await() 方法都将立即返回, 线程状态为 isDone() = true; isCancelled() = false;

Unsafe 的子类作为 Channel 的内部类, 负责处理底层 NIO 相关的 I/O 事件. Channel 则使用责任链的方式通过 ChannelPipeline 将事件提供给用户自定义处理.

NioEventLoopGroup 构造方法中, 创建 NioEventLoop, 创建

你可能感兴趣的:(Netty)