Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

Netty fix epoll bug

发表于 2018-12-15

JDK NIO的epoll bug,会导致Selector空轮询,最终导致CPU 100%

Bug出现的原因

若Selector的轮询结果一直为空,没有新消息处理,则发生空轮询,CPU使用率100%(在selector中,即使是select轮询事件为0的话,照样不断的从select本应该阻塞状态下wake up出来)

Netty的解决方案

  • 对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数
  • 若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。
  • 重建Selector,将出现bug的Selector上的channel重新注册到新的Selector上,并将原来的Selector关闭,使用新的Selector进行替换。

JDK源码分析:

JDK NIO执行代码如下:

执行selector.select()方法返回keys是0,所以本应该对key值进行遍历的事件处理根本没有执行,又回到最上面的while(true)循环,循环往复,不断的轮询,直到系统出现100%的CPU情况,最终导致程序崩溃。

Netty源码分析

具体位置在实现类NioEventLoop中:

netty会在每次进行 selector.select(timeoutMillis) 之前记录一下开始时间currentTimeNanos,在select之后记录一下结束时间,判断select操作是否至少持续了timeoutMillis时间;selectCnt记录空轮询次数

重建selector

当发生epoll bug,则创建一个新的Selector,将出现bug的Selector上的channel重新注册到新的Selector上,关闭bug的Selector,使用新的Selector进行替换 :

Netty的解决策略:

  • 根据该bug的特征,首先侦测该bug是否发生
  • 将问题Selector上注册的Channel转移到新建的Selector上
  • 老的问题Selector关闭,使用新建的Selector替换

参考

NIO的epoll空轮询bug

Netty源码分析 解决NIO的epoll死循环bug

java 和netty epoll实现

发表于 2018-12-14

Java NIO根据操作系统不同, 针对Selector有不同的默认实现:

  • macosx:KQueueSelectorProvider
  • solaris:DevPollSelectorProvider
  • Linux:EPollSelectorProvider (Linux kernels >= 2.6)或 PollSelectorProvider
  • windows: WindowsSelectorProvider

为什么netty还要提供一个基于epoll的实现

自4.0.16起, Netty为Linux通过JNI的方式提供了native socket transport.

  • NioEventLoopGroup → EpollEventLoopGroup
  • NioEventLoop → EpollEventLoop
  • NioServerSocketChannel → EpollServerSocketChannel
  • NioSocketChannel → EpollSocketChannel

原因:

  1. Netty的 epoll transport使用 epoll边缘触发 而 java nio 使用 epoll水平触发(在这个模式下,io来了数据,就只通知这些io设备对应的fd,上次通知过的fd不再通知,内核不用扫描一大堆fd)
  2. netty epoll transport 暴露了更多的java nio没有的配置参数, 如 TCP_CORK, SO_REUSEADDR等

参考

Netty native transports

Why native epoll support is introduced in Netty?

高性能网络服务器编程:为什么linux下epoll是最好,Netty要比NIO好?

Netty Server Demo

发表于 2018-12-14

Maven添加依赖

1
2
3
4
5
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.29.Final</version>
</dependency>

Server Demo

模拟请求/响应model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.zsr.test.netty;
public class RequestData {
private int intValue;
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
}
// return RequestData inValue * 2
public class ResponseData {
private int intValue;
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
}

服务端对请求解码

1
2
3
4
5
6
7
8
public class ServerRequestDecoder extends ReplayingDecoder<RequestData> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
RequestData data = new RequestData();
data.setIntValue(in.readInt());
out.add(data);
}
}

服务端对响应编码

1
2
3
4
5
6
public class ServerResponseEncoder extends MessageToByteEncoder<ResponseData> {
@Override
protected void encode(ChannelHandlerContext ctx, ResponseData msg, ByteBuf out) throws Exception {
out.writeInt(msg.getIntValue());
}
}

服务端具体业务处理

1
2
3
4
5
6
7
8
9
10
public class ServerProcessingHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
RequestData requestData = (RequestData) msg;
ResponseData responseData = new ResponseData();
responseData.setIntValue(requestData.getIntValue() * 2);
ctx.writeAndFlush(responseData);
System.out.println("server receive request: " + requestData.getIntValue());
}
}

启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class NettyTestServer {
public static void main(String[] args) throws Exception {
new NettyTestServer(8080).start();
}
public void start() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerRequestDecoder(), new ServerResponseEncoder(),
new ServerProcessingHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(port)).sync();
System.out.println(
NettyTestServer.class.getName() + " started and listen on " + channelFuture.channel().localAddress());
channelFuture.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}

参考

Introduction to Netty

Netty的那点事儿

Netty初探

发表于 2018-12-12
1
2
3
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

为什么不选择JAVA原生NIO

  • NIO 的类库和 API 繁杂,使用麻烦:需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
  • 编写出高质量的 NIO 程序需要具备其他的额外技能做铺垫:例如熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,必须对多线程和网路编程非常熟悉。
  • NIO 编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等。
  • JDK NIO的 Epoll Bug,它会导致Selector空轮询,最终导致CPU 100%

为什么选择Netty

  • API使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG;

Netty架构设计

Netty采用了比较典型的三层网络架构进行设计,逻辑架构图如下所示:

  • 传输服务:支持 BIO 和 NIO
  • 容器集成:支持 OSGI、JBossMC、Spring、Guice容器
  • 协议支持:HTTP、Protobuf、二进制、WebSocket等一系列常见协议都支持。还支持通过实行编码解码逻辑来实现自定义协议
  • Core 核心:可扩展事件模型、通用通信 API、支持零拷贝的 ByteBuf 缓冲对象

高性能的三大要素

  1. 传输:用什么样的通道将数据发送给对方,BIO、NIO 或者 AIO,IO 模型在很大程度上决定了框架的性能。
  2. 协议:采用什么样的通信协议,HTTP 或者内部私有协议。协议的选择不同,性能模型也不同。相比于公有协议,内部私有协议的性能通常可以被设计的更优。
  3. 线程模型:数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,Reactor 线程模型的不同,对性能的影响也非常大。

Netty高性能原因

IO模型

Netty的IO线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端Channel,由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁IO阻塞导致的线程挂起

零拷贝

在后面的文章专门详解

内存池

在后面的文章专门详解

Netty 线程模型

Netty 主要基于主从 Reactor 多线程模型(如下图)做了一定的修改,其中主从 Reactor 多线程模型有多个 Reactor:

  • MainReactor负责客户端的连接请求,并将请求转交给 SubReactor
  • SubReactor负责相应通道的IO读写请求
  • 非IO请求(具体逻辑处理)等待 worker threads 进行处理

注意:Netty 的线程模型基于主从 Reactor多线程,借用了 MainReactor 和 SubReactor 的结构。但是Netty实际实现上 SubReactor 和 Worker 线程在同一个线程池中

1
2
3
4
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)

Netty里对应mainReactor的角色叫做Boss,而对应subReactor的角色叫做Worker

bossGroup 和 workerGroup 这两个 group 均是线程池:

  • bossGroup 线程池则只是在 Bind 某个端口后,获得其中一个线程作为 MainReactor,专门处理端口的 Accept 事件
  • workerGroup 线程池会被各个SubReactor 和 Worker 线程充分利用

Netty线程模型

下图来自组内大牛分享,盗来使用~~:

注意:服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务(监听一个端口)的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费。

参考

Introduction to Netty

Netty的那点事儿

NIO Reactor 模型 & Netty 线程模型

do we need more than a single thread for boss group?

Reactor 模型

发表于 2018-12-07

服务端如何处理请求:后端接收到一个请求,简化下会做以下四个操作:

  1. 建立连接
  2. 读取数据
  3. 业务操作
  4. 写回数据

Reactor的核心思想:Reactor 模式也叫 Dispatcher 模式,将关注的IO事件注册到多路复用器上,一旦有IO事件触发,将事件分发到事件处理器中,执行就绪IO事件对应的处理函数中。模型中有三个重要的组件:

  • Reactor:在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。
  • Handlers:处理程序执行IO事件要完成的实际事件,Reactor通过调度处理程序来响应IO事件,处理程序执行非阻塞操作。

Reactor模式演进

Reactor 模型取决于 Reactor 的数量和 Hanndler 线程数量的不同,Reactor 模型有 3 个变种:

  • 单线程Reactor模式:单Reactor单线程Hanndler
  • 多线程Reactor模式:单Reactor多线程Hanndler
  • 主从Reactor模式:主从Reactor多线程Hanndler

Reactor 就是一个执行 while (true) { selector.select(); …} 循环的线程,会源源不断的产生新的事件,称作反应堆很贴切。

单线程Reactor模式

单线程Reactor模式 : 建立连接,读取/写回数据和业务操作都在同一个Reactor线程中执行,虽然Reactor的读取写回不会造成阻塞,但是业务操作就很可能造成阻塞;并且单线程不能充分利用CPU多核优势,因此处理能力是有限的,对于小量连接情况下问题不大,对于大量链接情况下,单个NIO线程因处理能力有限会导致连接大量超时。

多线程Reactor模式

将非IO的业务逻辑操作从Reactor线程上卸载,以此来加速Reactor线程对IO请求的响应。

多线程Reactor模式:多线程模式下把业务操作(decode,compute,encode)等放到线程池中处理,保证Reactor线程不会阻塞,而Reactor仍为单个线程仍然要处理连接和读取/写回操作,其承担连接负载量上来时仍然承受很大压力(例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能)

主从Reactor模式

主从Reactor模式:相比多线程Reactor模式,主从Reactor模式下把IO读取/写回两个操作放在 从Reactor线程池 中执行, 主Reactor线程也就是Acceptor只负责建立连接,建立之后将其注册到 从Reactor线程 中,因此大大提高了负载能力。同时为了避免并发问题还要保证一个连接只能注册一个Reactor线程上

  • MainReactor负责客户端的连接请求,并将请求转交给 SubReactor
  • SubReactor负责相应通道的IO读写操作
  • 非IO请求(具体逻辑处理)的任务则会交由工作线程池处理

好处:因为subReactor也会执行一些比较耗时的IO操作,例如消息的读写,使用多个线程去执行,则更加有利于发挥CPU的运算能力,减少IO等待时间。

参考

Scalable IO in Java

Netty–Reactor模型的应用

123…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces