Netty源码分析---processSelectedKeys() vs runAllTasks()

相关文章链接

位运算详解

waken方法详解

ThreadPerTaskExecutor与线程创建详解

processSelectedKeys() vs runAllTasks()

NioServerSocketChannel-Unsafe初始化详解

NioEventLoop的run方法详解

NioEventLoopGroup深度解析

inEventLoop方法详解

executionMask详解

Netty源码分析–认真系列(一)

Netty源码分析–认真系列(二)

processSelectedKeys() vs runAllTasks()

核心区别

简单来说:

  • processSelectedKeys():处理 IO 事件(网络读写)
  • runAllTasks():处理 普通任务(业务逻辑)

一、processSelectedKeys() - 处理 IO 事件

1. 什么是 IO 事件?

IO 事件就是网络相关的事件,由 Selector 监听到的。

IO 事件的类型:
├─ OP_ACCEPT  (16)  // 服务端接受新连接
├─ OP_CONNECT (8)   // 客户端连接完成
├─ OP_READ    (1)   // 有数据可读
└─ OP_WRITE   (4)   // 可以写数据

2. processSelectedKeys() 做什么?

private void processSelectedKeys() {
    // 遍历所有准备好的 Channel
    for (int i = 0; i < selectedKeys.size; ++i) {
        SelectionKey k = selectedKeys.keys[i];
        AbstractNioChannel ch = (AbstractNioChannel) k.attachment();
        
        // 处理这个 Channel 的 IO 事件
        processSelectedKey(k, ch);
    }
}

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    int readyOps = k.readyOps();  // 获取就绪的操作类型
    
    // 1. 处理连接完成事件
    if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        unsafe.finishConnect();
    }
    
    // 2. 处理写事件
    if ((readyOps & SelectionKey.OP_WRITE) != 0) {
        ch.unsafe().forceFlush();  // 刷新写缓冲区
    }
    
    // 3. 处理读事件或接受事件
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0) {
        unsafe.read();  // 读取数据或接受新连接
    }
}

3. 实际例子

场景:客户端发送数据到服务端

客户端:
    channel.writeAndFlush("Hello Server");
    数据通过网络发送

服务端 EventLoop:
    selector.select()  // 检测到 OP_READ 事件processSelectedKeys()  // 处理 IO 事件
    ↓
    找到对应的 Channel
    ↓
    检测到 OP_READ 事件
    ↓
    unsafe.read()  // 从 Socket 读取数据
    ↓
    读取到:"Hello Server"
    ↓
    触发 pipeline.fireChannelRead("Hello Server")
    ↓
    数据流经 pipeline 中的 handlers

关键点:processSelectedKeys() 是在处理"网络层"的事件。


二、runAllTasks() - 处理普通任务

1. 什么是普通任务?

普通任务是通过 execute() 方法提交到 taskQueue 中的任务。

普通任务的来源:
├─ 用户代码提交的任务
├─ Netty 内部提交的任务(如 register、bind、connect)
├─ 定时任务
└─ 其他异步操作

2. runAllTasks() 做什么?

protected boolean runAllTasks(long timeoutNanos) {
    // 1. 从定时任务队列取出到期的任务
    fetchFromScheduledTaskQueue();
    
    // 2. 计算截止时间
    final long deadline = nanoTime() + timeoutNanos;
    long runTasks = 0;
    
    // 3. 循环执行任务
    for (;;) {
        Runnable task = pollTask();  // 从 taskQueue 取任务
        if (task == null) {
            break;  // 没有任务了
        }
        
        safeExecute(task);  // 执行任务
        runTasks++;
        
        // 每 64 个任务检查一次时间
        if ((runTasks & 0x3F) == 0) {
            if (nanoTime() >= deadline) {
                break;  // 超时了,停止执行
            }
        }
    }
    
    return true;
}

3. 实际例子

场景:注册 Channel

// main 线程
ServerBootstrap b = new ServerBootstrap();
ChannelFuture f = b.bind(port);

// 内部流程
initAndRegister()
    ↓
eventLoop.register(channel)
    ↓
eventLoop.execute(new Runnable() {  // 提交任务到 taskQueue
    public void run() {
        register0(promise);  // 这是一个普通任务
    }
});

// EventLoop 线程
runAllTasks()  // 处理普通任务
    ↓
从 taskQueue 取出 register0 任务
    ↓
执行 register0
    ↓
doRegister()  // 将 Channel 注册到 Selector
    ↓
pipeline.fireChannelRegistered()

关键点:runAllTasks() 是在处理"应用层"的任务。


三、两者的区别对比

对比表格

特性processSelectedKeys()runAllTasks()
处理对象IO 事件(网络事件)普通任务(业务逻辑)
数据来源Selector(JDK NIO)taskQueue(Netty)
触发方式网络事件触发代码主动提交
处理内容读、写、连接、接受任意 Runnable
执行时机select() 返回后每次循环都执行
是否阻塞不阻塞(快速处理)可能耗时(业务逻辑)

形象比喻

EventLoop 就像一个餐厅服务员:

processSelectedKeys():
    服务客人(处理网络 IO)
    - 客人叫服务员(网络事件)
    - 服务员给客人上菜(读数据)
    - 服务员收客人的盘子(写数据)
    - 这是服务员的主要工作

runAllTasks():
    做杂活(处理普通任务)
    - 打扫卫生(业务逻辑)
    - 整理餐具(内部任务)
    - 准备食材(定时任务)
    - 这是服务员的额外工作

四、完整的执行流程

场景:客户端发送数据,服务端处理并回复

【第 1 步:客户端发送数据】
客户端:
    channel.writeAndFlush("Hello");

【第 2 步:服务端 EventLoop 检测到事件】
服务端 EventLoop:
    selector.select()  // 检测到 OP_READ 事件
    返回

【第 3 步:处理 IO 事件】
processSelectedKeys()
    ↓
找到对应的 Channel
    ↓
检测到 OP_READ 事件
    ↓
unsafe.read()
    ↓
从 Socket 读取数据:"Hello"
    ↓
触发 pipeline.fireChannelRead("Hello")
    ↓
数据流经 pipeline:
    Decoder → 解码
    BusinessHandler → 处理业务逻辑
        ↓
        在 BusinessHandler 中:
        ctx.writeAndFlush("World");  // 回复客户端
        ↓
        这会提交一个 write 任务到 taskQueue

【第 4 步:处理普通任务】
runAllTasks()
    ↓
从 taskQueue 取出 write 任务
    ↓
执行 write 任务
    ↓
将 "World" 写入 Socket 缓冲区
    ↓
如果缓冲区满了,注册 OP_WRITE 事件
    ↓
下次循环的 processSelectedKeys() 会处理 OP_WRITE 事件

五、ioRatio 的作用

现在我们理解了两种任务的区别,再来看 ioRatio:

final int ioRatio = this.ioRatio;  // 默认 50

if (ioRatio == 100) {
    // 不限制任务执行时间
    try {
        processSelectedKeys();  // 处理 IO 事件
    } finally {
        runAllTasks();          // 处理所有任务
    }
} else {
    // 限制任务执行时间
    final long ioStartTime = System.nanoTime();
    try {
        processSelectedKeys();  // 处理 IO 事件
    } finally {
        final long ioTime = System.nanoTime() - ioStartTime;
        // 根据 IO 时间计算任务执行时间
        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
    }
}

ioRatio 的含义

ioRatio 控制 IO 事件处理和普通任务处理的时间比例。

ioRatio = 50(默认):
    IO 时间 : 任务时间 = 50 : 50 = 1 : 1
    
    如果处理 IO 事件花了 100ms
    那么处理任务最多花 100ms
    
    计算:100 * (100 - 50) / 50 = 100ms

ioRatio = 70:
    IO 时间 : 任务时间 = 70 : 30
    
    如果处理 IO 事件花了 100ms
    那么处理任务最多花 43ms
    
    计算:100 * (100 - 70) / 70 ≈ 43ms

ioRatio = 100:
    不限制任务执行时间
    处理完所有 IO 事件
    然后处理完所有任务

为什么需要 ioRatio?

平衡两种工作:

如果只处理 IO 事件:
    ✗ 任务队列会堆积
    ✗ 业务逻辑得不到执行

如果只处理任务:
    ✗ IO 事件得不到及时处理
    ✗ 网络数据会丢失或延迟

通过 ioRatio 平衡:
    ✓ IO 事件及时处理
    ✓ 任务也能执行
    ✓ 两者都不会饿死

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值