|
| 1 | +# EventLoop |
| 2 | +EventLoop 是Netty的事件循环线程模型,它负责: |
| 3 | + |
| 4 | + - 监听 I/O 事件,比如连接、读、写 |
| 5 | + - 执行Channel相关任务 |
| 6 | + - 驱动 pipeline 里的 handler 回调 |
| 7 | + - 保证同一个Channel的事件通常在同一个线程串行执行 |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +EventLoop解决以下问题: |
| 12 | + |
| 13 | + - 高效管理大量连接 |
| 14 | + - 避免一个连接一个线程的高成本 |
| 15 | + - 减少并发锁竞争 |
| 16 | + - 让网络事件处理更有序、可控 |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | +一句话:EventLoop 就是 Netty 用来轮询 I/O 事件并执行 Channel 任务的核心线程。 |
| 21 | + |
| 22 | +## EventLoop实现 |
| 23 | +EventLoop是通用抽象,负责: |
| 24 | + |
| 25 | + - 执行任务 |
| 26 | + - 注册/管理Channel |
| 27 | + - 处理 I/O 事件 |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +具体底层怎么等 I/O 事件,由传输实现决定: |
| 32 | + |
| 33 | + - NIO:基于 Java Selector |
| 34 | + - epoll:基于 Linux epoll |
| 35 | + - kqueue:基于 BSD/macOS kqueue |
| 36 | + - io_uring:基于 Linux io_uring |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | +EventLoop 通用调度模型,Transport / IoHandler 决定底层 I/O 轮询实现。 |
| 41 | + |
| 42 | + |
| 43 | + |
| 44 | +## 网络事件\socket是什么 |
| 45 | +网络事件就是 socket 状态变化或 I/O 就绪通知。常见包括: |
| 46 | + |
| 47 | + - 有新连接进来:accept |
| 48 | + - 连接建立完成:connect |
| 49 | + - 有数据可读:read |
| 50 | + - 可以继续写数据:write |
| 51 | + - 连接关闭或异常:close / error |
| 52 | + |
| 53 | +操作系统告诉 Netty“这个连接现在可以做某件事了”,这就是网络事件。 |
| 54 | + |
| 55 | + |
| 56 | + |
| 57 | +那socket是什么呢? |
| 58 | + |
| 59 | +Socket是应用程序和网络之间的通信端点。可以理解成:程序用来收发网络数据的“连接句柄”。比如 TCP 通信里: |
| 60 | + |
| 61 | +```plain |
| 62 | +客户端 Socket <====网络====> 服务端 Socket |
| 63 | +``` |
| 64 | + |
| 65 | +程序通过 socket: |
| 66 | + |
| 67 | + - 连接远程服务 |
| 68 | + - 发送数据 |
| 69 | + - 接收数据 |
| 70 | + - 关闭连接 |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | +socket 是程序进行网络通信的入口。 |
| 75 | + |
| 76 | +# Pipeline |
| 77 | +Pipeline 是 Netty 里处理请求/响应的流水线,里面的 Handler 分步骤完成拆包、编解码、序列化、业务分发。Netty Client和Netty Server的pipeline都是用的DefaultChannelPipeline,但是怎么区分是Netty Client的pipeline还是Netty Server的pipeline呢? |
| 78 | + |
| 79 | +靠 Channel 类型和创建来源 区分,不靠 DefaultChannelPipeline 类型区分。DefaultChannelPipeline 本身是通用实现,Client 和 Server 都用它。但它绑定的 Channel 不同: |
| 80 | + |
| 81 | +```plain |
| 82 | +客户端连接: |
| 83 | +NioSocketChannel |
| 84 | + -> new DefaultChannelPipeline(this) |
| 85 | + -> Client Pipeline |
| 86 | +
|
| 87 | +服务端监听端口: |
| 88 | +NioServerSocketChannel |
| 89 | + -> new DefaultChannelPipeline(this) |
| 90 | + -> Server Boss Pipeline |
| 91 | +
|
| 92 | +服务端接收到的客户端连接: |
| 93 | +NioSocketChannel |
| 94 | + -> new DefaultChannelPipeline(this) |
| 95 | + -> Server Child Pipeline |
| 96 | +``` |
| 97 | + |
| 98 | +也就是说: |
| 99 | + |
| 100 | +```plain |
| 101 | +pipeline.channel() |
| 102 | +``` |
| 103 | + |
| 104 | + Pipeline 类型都一样,属于谁取决于它绑定的是哪个 Channel,以及这个 Channel 是由客户端 Bootstrap创建,还是服务端ServerBootstrap创建。 |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +一个pipeline既可以处理入站请求,也可以处理出站请求,只不过方向不一样。 |
| 109 | + |
| 110 | +```plain |
| 111 | +入站 Inbound:读数据、解码、业务处理 |
| 112 | +出站 Outbound:写数据、编码、发送 |
| 113 | +
|
| 114 | +入站:Head -> Tail |
| 115 | +出站:Tail -> Head |
| 116 | +``` |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | +```plain |
| 121 | +一个 ChannelPipeline |
| 122 | +┌──────────────────────────────────────────────┐ |
| 123 | +│ │ |
| 124 | +│ Head ── H1 ── H2 ── H3 ── H4 ── Tail │ |
| 125 | +│ │ |
| 126 | +└──────────────────────────────────────────────┘ |
| 127 | +
|
| 128 | +
|
| 129 | +入站 Inbound:从 Head 到 Tail |
| 130 | +Socket 收到数据 |
| 131 | + ↓ |
| 132 | +Head ──> H1 ──> H2 ──> H3 ──> H4 ──> Tail |
| 133 | + 解码 反序列化 业务处理 |
| 134 | +
|
| 135 | +
|
| 136 | +出站 Outbound:从 Tail 到 Head |
| 137 | +业务调用 writeAndFlush() |
| 138 | + ↓ |
| 139 | +Tail ──> H4 ──> H3 ──> H2 ──> H1 ──> Head |
| 140 | + 序列化 编码 写 Socket |
| 141 | +``` |
| 142 | + |
| 143 | +## 出站和入站 |
| 144 | +Netty出站——ChannelInboundHandler,是指当前 Channel 将数据写出到网络。比如 RPC Client 发送请求,或 RPC Server 返回响应,都会经过出站流程:Java 对象序列化、协议编码/封装,转成 ByteBuf,再通过 Socket 发送出去。 |
| 145 | + |
| 146 | +Netty入站——ChannelOutboundHandler,是指当前 Channel 从网络读取数据。比如 RPC Server 接收请求,或 RPC Client 接收响应,都会经过入站流程:读取 ByteBuf,按协议处理粘包半包,解码协议,再反序列化成 Java 对象。 |
| 147 | + |
| 148 | +## 为什么入站:HEAD->TAIL,出站:TAIL->HEAD? |
| 149 | +核心逻辑就是:**<font style="color:#DF2A3F;">Head 更靠近 Socket / 底层 I/O,Tail 更靠近业务处理端。</font>** |
| 150 | + |
| 151 | +因为 Netty 把 `Head` 设计成**靠近底层 Socket 的入口**,`Tail` 设计成**靠近业务处理的末端**。 |
| 152 | + |
| 153 | +所以网络数据进来时: |
| 154 | + |
| 155 | +```plain |
| 156 | +Socket 读到数据 |
| 157 | + ↓ |
| 158 | +HeadContext |
| 159 | + ↓ |
| 160 | +InboundHandler1 |
| 161 | + ↓ |
| 162 | +InboundHandler2 |
| 163 | + ↓ |
| 164 | +TailContext |
| 165 | +``` |
| 166 | + |
| 167 | +也就是: |
| 168 | + |
| 169 | +```plain |
| 170 | +Head -> Tail |
| 171 | +``` |
| 172 | + |
| 173 | +反过来,业务要写数据出去时,是从业务侧往 Socket 走: |
| 174 | + |
| 175 | +```plain |
| 176 | +Tail -> Head |
| 177 | +``` |
| 178 | + |
| 179 | +最后到 `HeadContext`,由它调用底层 unsafe 把数据写到 Socket。 |
| 180 | + |
| 181 | +# Channel |
| 182 | +Channel是Netty对网络连接/通信端点的抽象,也就是Netty里对 socket 连接或监听端口的封装。它代表一个可进行 I/O 操作的对象,比如: |
| 183 | + |
| 184 | ++ 服务端监听端口:ServerSocketChannel |
| 185 | ++ 客户端连接:SocketChannel |
| 186 | ++ UDP 通信:DatagramChannel |
| 187 | + |
| 188 | + |
| 189 | + |
| 190 | +它主要负责: |
| 191 | + |
| 192 | ++ 读写数据 |
| 193 | ++ 绑定/连接/关闭 |
| 194 | ++ 持有关联的EventLoop |
| 195 | ++ 持有ChannelPipeline |
| 196 | + |
| 197 | +# EventLoopGroup、EventLoop、Channel关系 |
| 198 | +在Netty中,一般这样设置: |
| 199 | + |
| 200 | +```plain |
| 201 | +EventLoopGroup bossGroup = new NioEventLoopGroup(1); |
| 202 | +EventLoopGroup workerGroup = new NioEventLoopGroup(); |
| 203 | +``` |
| 204 | + |
| 205 | +bossGroup:负责监听端口、accept 新连接,通常 1 个线程就够。workerGroup:负责已连接客户端的读写 I/O,默认线程数通常是 CPU核数 * 2。 |
| 206 | + |
| 207 | + |
| 208 | + |
| 209 | +EventLoop、线程、channel之间的关系: |
| 210 | + |
| 211 | + - 1 个 EventLoop 通常绑定 1 个线程 |
| 212 | + - 1 个 EventLoopGroup 包含多个 EventLoop |
| 213 | + - 1 个 Channel 注册到 1 个 EventLoop |
| 214 | + - 1个EventLoop会有多个channel,一个EventLoop会轮训多个channel的事件 |
| 215 | + |
| 216 | + |
| 217 | + |
| 218 | +在Netty中,一个EventLoop线程不是被某个Channel独占的。它会轮询处理多个Channel的事件: |
| 219 | + |
| 220 | +```plain |
| 221 | +EventLoop-1 |
| 222 | + -> Channel A read |
| 223 | + -> Channel B read |
| 224 | + -> Channel C write |
| 225 | + -> 定时任务 |
| 226 | + -> 下一轮 select |
| 227 | +``` |
| 228 | + |
| 229 | + |
| 230 | + |
| 231 | +总结:BossGroup 里通常配置一个或少数几个 EventLoop。Netty 服务端调用 bind() 时,会创建并将 XxxServerSocketChannel 注册到 BossGroup 的某个 EventLoop 上。这个 EventLoop 主要负责轮询 XxxServerSocketChannel 上的客户端新连接事件,也就是 TCP accept 事件。当 accept 到新连接后,Netty 会创建对应的 XxxSocketChannel,并将它注册到 WorkerGroup 按策略选出的某个 EventLoop 上。后续该客户端连接的 read/write 事件,都由这个 XxxSocketChannel 绑定的 Worker EventLoop 负责轮询和处理。 |
| 232 | + |
| 233 | +不同的EventLoop底层使用的轮询机制不一样,不管底层是用的:Selector / epoll / kqueue / io_uring / 阻塞 I/O,都需要轮询事件。 |
| 234 | + |
| 235 | + - NioEventLoop -> Selector |
| 236 | + - EpollEventLoop -> Linux epoll |
| 237 | + - KQueueEventLoop -> BSD/macOS kqueue |
| 238 | + - IoUringEventLoop -> Linux io_uring |
| 239 | + - OioEventLoop -> 阻塞 I/O 模型 |
| 240 | + |
| 241 | + |
| 242 | + |
0 commit comments