换一个角度理解Netty的异步

Netty之所以说是异步阻塞网络框架时因为通过NioSocketChannel的write系列方法向连接里面写入数据的时候是非阻塞的(这个地方细化就是:通过channel写数据,其实完整的走了整个pipeline),马上就会返回,即使调用写入的线程是我们的业务线程。

(在应用中,我们可能将channel缓存起来,然后在业务线程中进行调用)

这是Netty通过在ChannelPipeline中判断调用NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程来实现的,如果发现不是则会把写入请求封装WriteTask投递到其对应的NioEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询链接套接字的读写事件时候捎带从队列里面取出来执行。总结来说就是每个NioSocketChannel对应的读写事件都是在其对应的NioEventloop管理的单线程内执行,对同一个NioSocketChannel不存在并发读写,所以无需加锁处理。

三点不理解的地方:

  1. 调用Channel上的write方法为什么由ChannelPipeline判断调用write方法的调用线程是不是NioEventLoop中的线程。

  2. 投递到其对应的NioEventLoop中的队列里面,这个技术上是如何实现的。

  3. NioEventLoop线程伦旭套接字的读写事件,这个又是如何实现的呢?

一个高级问题

NioEventLoop中的线程负责监听注册到Selector上的所有连接的读写事件和处理队列里面的消息,那么会不会导致由于处理队列里面任务耗时太长导致来不及处理连接的读写事件?

(从这个问题可以看出来,我们需要处理连接的读写事件,还需要处理队列中的事件)

Netty默认是采用时间均分策略来避免某一方处于饥饿状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

// 记录开始处理事件
final long ioStartTime = System.nanoTime();
try{
    // 处理连接套接字的读写事件
    processSelectedKeys();
} finally {
    // 计算连接套接字处理耗时,ioRatio默认为50
    final long ioTime = System.nanoTime() - ioStartTime;
    // 运行队列里面任务
    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}

参考资料

  1. 谈谈Netty的线程模型