从Netty到EPollSelectorImpl学习Java NIO

简介: 终于可以在写了几篇鸡汤文后,来篇技术文章了,:),题图是Trustin Lee,Mina/Netty都是他搞的,对Java程序员尤其是写通讯类的都产生了巨大影响,向他致敬! 在上周查一个内存OOM的问题之前,我一直觉得自己对Java NIO应该还是比较懂的,君不见N年前我曾经写过一篇《NFS-RP

终于可以在写了几篇鸡汤文后,来篇技术文章了,:),题图是Trustin Lee,Mina/Netty都是他搞的,对Java程序员尤其是写通讯类的都产生了巨大影响,向他致敬!

在上周查一个内存OOM的问题之前,我一直觉得自己对Java NIO应该还是比较懂的,君不见N年前我曾经写过一篇《NFS-RPC框架优化过程(从37K到168K)》(尴尬的发现,上次导blog记录的时候竟然丢了一些文章,于是这文章link就不是自己的blog了),从那优化经历来说理论上对Netty的原理应该已经是比较清楚了才对,结果在查那个内存OOM的问题的时候,发现自己还是too young too navie,看到的现象是EPollSelectorImpl里的fdToKey有一大堆数据,就傻眼了,完全不知道这是为什么,当时就在公众号上发了个文本信息咨询大家,有不少同学给了我回复,另外滴滴打车的架构师欧阳康给了我一篇文章来说明EPollSelectorImpl这个部分的原理(强烈推荐,比我写的这篇会更深入到os层),本文就是综合了大家给我的点拨,来写写从Netty到EPollSelectorImpl的相关逻辑。

带着问题去看代码会比较的快和容易,我这次带着这几个问题:
1. EPollSelector里的fdToKey里的一大堆数据是怎么出现的;
2. Netty以及Java的EPoll部分代码是如何让N多连接的处理做到高效的,当然这主要是因为epoll,不过Java部分的相关实现也是重要的。

由于公众号贴代码不太方便,在这我就不贴大段的代码了,只摘取一些非常关键的代),代码部分我看的是Server部分,毕竟Server尤其是长连接类型的,通常会需要处理大量的连接,而且会主要是贴近我所关注的两个问题的相关代码。

Netty在初始化Server过程中主要做的事:
1. 启动用于处理连接事件的线程,线程数默认为1;
2. 创建一个EPollSelectorImpl对象;

在bind时主要做的事:
1. 开启端口监听;
2. 注册OP_ACCEPT事件;

处理连接的线程通过Selector来触发动作:

int selected = select(selector);

这个会对应到EPollSelectorImpl.doSelect,最关键的几行代码:

pollWrapper.poll(timeout);
int numKeysUpdated = updateSelectedKeys(); // 更新有事件变化的selectedKeys,selectedKeys是个Set结构

当numKeysUpdated>0时,就开始处理其中发生了事件的Channel,对连接事件而言,就是去完成连接的建立,连接建立的具体动作交给NioWorker来实现,每个NioWorker在初始化时会创建一个EPollSelectorImpl实例,意味着每个NioWorker线程会管理很多的连接,当建连完成后,注册OP_READ事件,注册的这个过程会调用到EPollSelectorImpl的下面的方法:

protected void implRegister(SelectionKeyImpl ski) {
SelChImpl ch = ski.channel;
fdToKey.put(Integer.valueOf(ch.getFDVal()), ski);
pollWrapper.add(ch);
keys.add(ski);
}

从这段代码就明白了EPollSelectorImpl的fdToKey的数据是在连接建立后产生的。

那什么时候会从fdToKey里删掉数据呢,既然放数据是建连接的时候,那猜测删除就是关连接的时候,翻看关连接的代码,最终会调到EPollSelectorImpl的下面的方法:

protected void implDereg(SelectionKeyImpl ski) throws IOException {
assert (ski.getIndex() >= 0);
SelChImpl ch = ski.channel;
int fd = ch.getFDVal();
fdToKey.remove(new Integer(fd));
pollWrapper.release(ch);
ski.setIndex(-1);
keys.remove(ski);
selectedKeys.remove(ski);
deregister((AbstractSelectionKey)ski);
SelectableChannel selch = ski.channel();
if (!selch.isOpen() && !selch.isRegistered())
((SelChImpl)selch).kill();
}

从上面代码可以看到,在这个过程中会从fdToKey中删除相应的数据。

翻代码后,基本明白了fdToKey这个部分,在Netty的实现下,默认会创建一个NioServerBoss的线程,cpu * 2的NioWorker的线程,每个线程都会创建一个EPollSelectorImpl,例如如果CPU为4核,那么总共会创建9个EPollSelectorImpl,每建立一个连接,就会在其中一个NioWorker的EPollSelectorImpl的fdToKey中放入SelectionKeyImpl,当连接断开时,就会相应的从fdToKey中删除数据,所以对于长连接server的场景而言,fdToKey里有很多的数据是正常的。

——————————-

第一个问题解决后,继续看第二个问题,怎么用比较少的资源高效的处理那么多连接的各种事件。

根据上面翻看代码的记录,可以看到在Netty默认的情况下,采用的是1个线程来处理连接事件,cpu * 2个NioWorker线程来处理读写事件。

连接动作因为很轻量,因此1个线程处理通常足够了,当然,客户端在设计重连的时候就得有避让机制,否则所有机器同一时间点重连,那就悲催了。

在分布式应用中,网络的读写会非常频繁,因此读写事件的高效处理就非常重要了,在Netty之上怎么做到高效也很重要,具体可以看看我之前写的那篇优化的文章,这里就只讲Netty之下的部分了,NioWorker线程通过
int selected = select(selector);
来看是否有需要处理的,selected>0就说明有需要处理,EPollSelectorImpl.doSelect中可以看到一堆的连接都放在了pollWrapper中,如果有读写的事件要处理,这里会有,这块的具体实现还得往下继续翻代码,这块没再继续翻了,在pollWrapper之后,就会updateSelectedKeys();,这里会把有相应事件发生的SelectionKeyImpl放到SelectedKeys里,在netty这层就会拿着这个selectedKeys进行遍历,一个一个处理,这里用多个线程去处理没意义的原因是:从网卡上读写的动作是串行的,所以再多线程也没意义。

所以基本可以看到,网络读写的高效主要还是ePoll本身做到,因为到了上层其实每次要处理的已经是有相应事件发生的连接,netty部分通过较少的几个线程来有效的处理了读写事件,之所以读写事件不能像连接事件一样用一个线程去处理,是因为读的处理过程其实是比较复杂的,从网卡cp出数据后,还得去看数据是否完整(对业务请求而言),把数据封装扔给业务线程等等,另外也正因为netty是要用NioWorker线程处理很多连接的事件,因此在高并发场景中保持NioWorker整个处理过程的快速,简单是非常重要的。

——————————

带着这两个问题比以前更往下的翻了一些代码后,确实比以前更了解Java NIO了,但其实还是说不到深入和精通,因为要更细其实还得往下翻代码,到OS层,所以我一如既往的觉得,其实Java程序员要做到精通,是比C程序员难不少的,因为技术栈更长,而如果不是从上往下全部打通技术栈的话,在查问题的时候很容易出现查到某层就卡壳,我就属于这种,所以我从来不认为自己精通Java的某部分。

最近碰到的另外一个问题也是由于自己技术栈不够完整造成排查进展一直缓慢,这问题现在还没结果,碰到的现象就是已经触发了netty避免ePoll bug的workaround,日志里出现:
Selector.select() returned prematurely 512 times in a row; rebuilding selector.
这个日志的意思是Selector.select里没有数据,但被连续唤醒了512次,这样的情况很容易导致一个cpu core 100%,netty认为这种情况出现时ePoll bug造成的,在这种情况下会采取一个workaround方法,就是rebuilding selector,这个操作会造成连接重建,对高并发场景来说,这个会造成超时等现象,所以影响还挺大的。
由于这个问题已经要查到os层,我完全无能为力,找了公司的一个超级高手帮忙查,目前的进展是看到有一个domain socket被close了,但epoll_wait的时候还是会选出这个fd,但目前还不知道为什么会出现这现象,所以暂时这问题还是存在着,有同学有想法的也欢迎给些建议。

来自:http://hellojava.info/?p=494

作者:阿里毕玄

目录
相关文章
|
3天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
14 3
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
99 43
Java学习十六—掌握注解:让编程更简单
|
10天前
|
存储 监控 Java
Java的NIO体系
通过本文的介绍,希望您能够深入理解Java NIO体系的核心组件、工作原理及其在高性能应用中的实际应用,并能够在实际开发中灵活运用这些知识,构建高效的Java应用程序。
26 5
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
2月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
45 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
38 2
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
59 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
2月前
|
前端开发 Java 应用服务中间件
Javaweb学习
【10月更文挑战第1天】Javaweb学习
39 2