Java基础(一):I/O多路复用模型及Linux中的应用
IO多路复用模型广泛的应用于各种高并发的中间件中,那么区别于其他模式他的优势是什么、其核心设计思想又是什么、其在Linux中是如何实现的?
I/O模型
I/O模型主要有以下五种:
- 同步阻塞I/O:I/O操作将同步阻塞用户线程
- 同步非阻塞I/O:所有操作都会立即返回,但需要不断轮询获取I/O结果
- I/O多路复用:一个线程监听多个I/O操作是否就绪,依然是阻塞I/O,需要不断去轮询是否有就绪的fd
- 信号驱动I/O:当I/O就绪后,操作系统发送SIGIO信号通知对应进程,避免空轮询导致占用CPU(linux中的信号驱动本质还是使用的epoll)
- 异步I/O:应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成
其中应用最广的当属I/O多路复用模型,其核心就是基于Reactor设计模式,仅一个线程就可以监听多个I/O事件,使得在高并发场景下节约大量线程资源
Reactor设计模式
处理WEB通常有两种请求模型:
- 基于线程:每个请求都创建一个线程来处理。并发越高,线程数越多,内存占用越高,性能也会越低,线程上下文切换造成性能损耗,线程等待IO也会浪费CPU时间。一般应用于并发量少的小型应用。
- 事件驱动:每个请求都由Reactor线程监听,当I/O就绪后,由Reactor将任务分发给对用的Handler。
显然事件驱动模型更适用于目前动辄几十万并发的场景。
网络服务器的基本处理模型如下:建立连接->读取请求->解析请求->处理服务->编码结果->返回结果。
基于网络服务器的基本模型,Reactor衍生出了以下三种模型。
1.单线程模型
Reactor单线程模型,指的是所有的I/O操作都在同一个NIO线程上面完成,NIO线程的职责如下:
- 作为NIO服务端,接收客户端的TCP连接
- 作为NIO客户端,向服务端发起TCP连接
- 读取通信对端的请求或者应答消息
- 向通信对端发送消息请求或者应答消息
Reactor线程负责多路分离套接字,Accept新连接,并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。
2.多线程模型
Reactor多线程模型与单线程模型最大区别就是引入了线程池,负责异步调用Handler处理业务,从而使其不会阻塞Reactor,它的流程如下:
- Reactor 对象通过 select 监控客户端请求事件,收到事件后,通过 dispatch 进行分发
- 如果是建立连接请求,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 对象处理完成连接后的各种事件
- 如果不是连接请求,则由 Reactor 对象会分发调用连接对应的 Handler 来处理
- Handler 只负责响应事件,不做具体的业务处理,通过 read 读取数据后,会分发给后面的 Worker 线程池的某个线程处理业务
- Worker 线程池会分配独立线程完成真正的业务,并将结果返回给 Handler
- Handler 收到响应后,通过 send 将结果返回给 Client
3.主从多线程模型
将连接请求句柄和数据传输句柄分开处理,使用单独的Reactor来处理连接请求句柄,提高数据传送句柄的处理能力。
服务端用于接收客户端连接的不再是1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(sub reactor线程池)的某个I/O线程上,由它负责SocketChannel的读写和编解码工作。
著名的Netty即采用了此种模式
Linux中的I/O多路复用
linux实现I/O多路复用,主要涉及三个函数select、poll、epoll,目前前两个已经基本不用了,但作为面试必考点还是应该知晓其原理。
几个重要概念:
- 用户空间和内核空间:为保护linux系统,将可能导致系统崩溃的指令定义为R0级别,仅允许在内核空间的进程使用,而普通应用则运行在用户空间,当应用需要执行R0级别指令时需要由用户态切换到内核态(极其耗时)。
- 文件描述符(File descriptor):当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符用于对应这个打开/新建的文件,其fd本质上就是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。
select
int select(int maxfd1, // 最大文件描述符个数,传输的时候需要+1
fd_set *readset, // 读描述符集合
fd_set *writeset, // 写描述符集合
fd_set *exceptset, // 异常描述符集合
const struct timeval *timeout);// 超时时间
select通过数组存储用户关心的fd并通知内核,内核将fd集合拷贝至内核空间,遍历后将就绪的fd集合返回
其缺点主要有以下几点:
- 最大支持的fd_size为1024(有争议?),远远不足以支撑高并发场景
- 每次涉及fd集合用户态到内核态切换,开销巨大
- 遍历fd的时间复杂度为O(n),性能并不好
poll
int poll(struct pollfd *fds, // fd的文件集合改成自定义结构体,不再是数组的方式,不受限于FD_SIZE
unsigned long nfds, // 最大描述符个数
int timeout);// 超时时间
struct pollfd {
int fd; // fd索引值
short events; // 输入事件
short revents; // 结果输出事件
};
poll技术与select技术实现逻辑基本一致,重要区别在于其使用链表的方式存储描述符fd,不受数组大小影响
说白了对于select的缺点poll只解决了第一点,依然存在很大性能问题
epoll
// 创建保存epoll文件描述符的空间,该空间也称为“epoll例程”
int epoll_create(int size); // 使用链表,现在已经弃用
int epoll_create(int flag); // 使用红黑树的数据结构
// epoll注册/修改/删除 fd的操作
long epoll_ctl(int epfd, // 上述epoll空间的fd索引值
int op, // 操作识别,EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL
int fd, // 注册的fd
struct epoll_event *event); // epoll监听事件的变化
struct epoll_event {
__poll_t events;
__u64 data;
} EPOLL_PACKED;
// epoll等待,与select/poll的逻辑一致
epoll_wait(int epfd, // epoll空间
struct epoll_event *events, // epoll监听事件的变化
int maxevents, // epoll可以保存的最大事件数
int timeout); // 超时时间
为了解决select&poll技术存在的两个性能问题,epoll应运而生
- 通过epoll_create函数创建epoll空间(相当于一个容器管理),在内核中存储需要监听的数据集合,通过红黑树实现,插入删除的时间复杂度为O(nlogn)
- 通过epoll_ctl函数来注册对socket事件的增删改操作,并且在内核底层通过利用mmap技术保证用户空间与内核空间对该内存是具备可见性,直接通过指针引用的方式进行操作,避免了大内存数据的拷贝导致的空间切换性能问题
- 通过ep_poll_callback回调函数,将就绪的fd插入双向链表fd中,避免通过轮询的方式获取,事件复杂度为O(1)
- 通过epoll_wait函数的方式阻塞获取rdlist中就绪的fd
EPOLL事件有两种模型 Level Triggered (LT) 和 Edge Triggered (ET):
- LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
- ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。
Don't let emotion cloud your judgment.
不要让情绪影响你的判断。
Java基础(一):I/O多路复用模型及Linux中的应用的更多相关文章
- 【java基础 13】两种方法判断hashmap中是否形成环形链表
导读:额,我介绍的这两种方法,有点蠢啊,小打小闹的那种,后来我查了查资料,别人都起了好高大上的名字,不过,本篇博客,我还是用何下下的风格来写.两种方法,一种是丢手绢法,另外一种,是迷路法. 这两种方法 ...
- Java基础之写文件——将素数写入文件中(PrimesToFile)
控制台程序,计算素数.创建文件路径.写文件. import static java.lang.Math.ceil; import static java.lang.Math.sqrt; import ...
- Java基础之写文件——在通道写入过程中的缓冲区状态(BufferStateTrace)
控制台程序,在Junk目录中将字符串“Garbage in, garbage out\n”写入到名为charData.txt的文件中. import static java.nio.file.Stan ...
- Java基础---集合框架---迭代器、ListIterator、Vector中枚举、LinkedList、ArrayList、HashSet、TreeSet、二叉树、Comparator
为什么出现集合类? 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式. 数组和集合类同是容器,有何不同? 数组虽然也可以存储对 ...
- Java基础加强之并发(三)Thread中start()和run()的区别
Thread中start()和run()的区别 start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法.start()不能被重复调用.run() : run()就和普通的成 ...
- [Java基础] 深入jar包:从jar包中读取资源文件
转载: http://hxraid.iteye.com/blog/483115?page=3#comments 我们常常在代码中读取一些资源文件(比如图片,音乐,文本等等).在单独运行的时候这些简单的 ...
- (Linux基础学习)第五章:Linux中的screen应用
第1节:安装screen1.加载系统镜像文件,因为screen的安装包在系统镜像文件中图001 2.列出系统上所有的磁盘[root@centos6 ~]# lsblk图002 3.安装screen应用 ...
- 大话java基础知识一之为什么java的主函数入口必须是public static void
为什么java的主函数入口必须是public static void main (String[] args); 很多写javaEE好几年的程序员经常会记得java的主函数就是这么写的,但实际上为什么 ...
- 第4节:Java基础 - 必知必会(中)
第4节:Java基础 - 必知必会(中) 本小节是Java基础篇章的第二小节,主要讲述抽象类与接口的区别,注解以及反射等知识点. 一.抽象类和接口有什么区别 抽象类和接口的主要区别可以总结如下: 抽象 ...
随机推荐
- (Element UI 组件 Table)去除单元格底部的横线
Element UI 组件 Table 有一个属性 border,添加它可以增加纵向边框,但是无法控制横线边框,因此即使是最简单的 el-table,也会包含一个底部横线. 这个底部横线其实是一个 b ...
- Unity 不能添加脚本组件,脚本类可能丢失
报错: 把脚本拖到游戏对象,显示如下 原因: 你可能修改了脚本名称,但此时Unity没有找到那个对应脚本名称的类
- Kubernetes架构原理
1.了解架构 在研究Kubernetes如何实现其功能之前,先具体了解下Kubernetes集群有哪些组件.Kubernetes集群分为两部分: Kubernetes控制平面 (工作)节点 具体看下这 ...
- 关于XXE漏洞
XXE漏洞 0x01.xxe是什么 介绍 XXE 之前,我先来说一下普通的 XML 注入,这个的利用面比较狭窄,如果有的话应该也是逻辑漏洞 1.1xml定义 XML用于标记电子文件使其具有结构性的标记 ...
- 资源:Redis下载地址
Redis的下载路径 http://download.redis.io/releases/ 3.x(支持集群) 2.x不支持集群 (推荐使用3.0+版本) 副版本号为偶数时,表示是稳定 ...
- [心得笔记]spring常用的三种依赖注入方式
一.目前使用最广泛的 @Autowired:自动装配 基于@Autowired的自动装配,默认是根据类型注入,可以用于构造器.接口.方法注入,使用方式如下: @Autowired 构造方法.方法.接口 ...
- 18 shell 重定向以及文件描述符
1.对重定向的理解 2.硬件设备和文件描述符 文件描述符到底是什么 3.Linux Shell 输出重定向 4.Linux Shell 输入重定向 5.结合Linux文件描述符谈重定向 6.Shell ...
- Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date'
我的情况是:在applicationContext.xml文件中配置 <bean id="member" class="com.entity.Member" ...
- linux学习之路第二天(xshell和xftp的使用图解)
以上xshell的连接过程 2.远程上传和下载文件xftp6 一定要换成22和sftp,否则连接不上 如果出现中文乱码的情况 先点击那个小齿轮 再点击选项 把编码编程UTF-8就ok了
- 关于中文版的manpages
可以从下面下载对应的包: https://code.google.com/p/manpages-zh/ https://github.com/lidaobing/manpages-zh 目前只有一部分 ...