今天推荐的文章是:https://cloud.tencent.com/developer/article/2465816?policyId=20240001&traceId=01jesmkgxa5qj7zm1pgw2rq8ac RocketMQ,轻量级拉取消费原理,文章从RocketMQ的架构讲解了消息队列的实际落地应用
在执行应用程序的过程中,会调用磁盘中的数据,而从磁盘中调用一个数据块到内存中的一次操作就叫做IO,而应用程序在调用磁盘的数据时,有多种调用策略,例如BIO、NIO、AIO等,本篇文章旨在介绍常见的IO模型
当客户端与服务端建立连接时,服务端需要进行IO操作来完成对应任务,这时客户端如果只是单纯等待,那么这个调用的过程就叫做BIO,即客户端调用服务端的链路中是阻塞等待执行的:
这个模型弊端很明显,当客户端进行调用的时候,不能够执行其他任务,这样会使得执行效率大大降低
这是对于BIO的优化,优化之处就在于,当用户程序发起对于内核的IO调用时,IO不会在拷贝数据后才返回给用户程序,而是在发现没有数据时,立刻返回错误:
这个模型对于数据不立刻使用的情况确实会提升效率,但是如果要请求的数据客户端马上就要使用,那么这个模型会与BIO没什么区别,同时每次进行用户态到内核态的请求调用也非常耗时,关于每次的请求调用,可以通过将对应请求文件信息的文件描述符传递给内核,让内核应用程序在内核态中进行循环遍历减少耗时,这一步操作主要由select、poll等函数完成(后面有介绍)
多路(有多个IO操作请求)复用(使用少量的线程监控多条连接的建立情况)旨在降低由于多个用户程序进行IO请求造成的内存占用:
看到这张图你可能觉得这与NIO没什么区别,但是核心就在于,每次进行IO请求的不再是单一read程序,而是select专用线程,它会负责一直进行IO请求,判断数据是否已经准备完毕,如果已经准备完毕,那么就会通知对应进程的read操作完成对于数据的读取,这里要注意的是,在select不断发起请求的同时,应用进程的read操作会一直处于阻塞过程中
这里处理select请求的线程主要有select函数完成,每次用户程序进行数据请求将文件描述符fd传递给内核程序后,select函数会维护一个固定大小的位图来保存传递的文件描述符集合,而我们有时会看到一个问题,poll函数与select函数有什么区别?它们都是IO操作过程中,用于进行IO请求的函数,但是不同点在于:
poll函数会维护一个动态数组,动态数组存储了对应的每一个文件描述符结构体,文件描述符结构体存储对应的文件描述符与事件类型,因此poll函数相比select函数支持更多的IO请求,同时在少量请求情况下,占用内存量也更少
而select与poll函数操作都会对文件描述符进行遍历,以此判断数据是否准备就绪,在高并发场景下,重复的循环遍历会导致性能下降
既然select与poll函数都只是单纯的循环遍历来判断数据是否准备完毕,那么是否有更加高效便捷的方式完成这步操作呢,epoll函数便可以实现这个特性,而epoll函数完成这个特性的关键就在于引入了红黑树结构与双向链表结构:
红黑树结构:当进行循环遍历时,函数会寻找该请求对应的文件描述符,这个遍历的过程是很耗时的,而我们知道,红黑树有查找、修改、添加都很高效的特性,因此在epoll函数中,文件描述符主要由红黑树结构维护,这样每次插入文件描述符后红黑树都会进行高效地结点调整,以保证查找操作能够高效进行
双向链表结构:当epoll函数在请求失败后,会再次调用epoll_wait函数判断数据是否准备完毕,它会对双向链表进行遍历,但是虽然也会循环遍历,不同的在于,这个双向链表中维护的是状态发生改变的文件信息,而不是所有的文件信息,当某个文件数据查找成功或者不存在时,对应的文件状态会发生改变,这时它会被添加到这个双向链表中,因此epoll_wait函数在进行遍历的时候会大大节省查找效率
因此epoll函数相较于select与poll函数对于IO请求的操作会大大节省查找效率
既然每次都要进行不断请求十分耗时,有没有一种方式能够减少请求调用的次数呢?那么我们可以使用信号式IO来完成,当用户read程序进行第一次请求返回error错误时,不会再次进行调用,而是由内核程序来判断数据是否准备完毕,如果准备完毕就会发出通知信息告诉用户程序数据已经准备完毕,之后完成从内核态到用户态的数据拷贝:
虽然这种IO模型看起来很实用,但是实际上应用场景很少,因为在当下应用网络协议通信的时代,网络协议每次进行连接请求的时候也会发出连接的事件信号,容易与IO请求信号发生干扰,因此现在广泛应用的主要是多路复用IO
最后隆重介绍我们的IO模型老大哥,异步请求IO,所谓异步,就是应用程序发出请求的时候,并不会由它本身执行,而是另外发起一个进程完成这个请求操作,而在IO请求中,对于异步的使用主要由内核应用程序完成,这其实是对于信号驱动式IO的优化,当内核发现数据准备就绪时,会直接将准备的数据拷贝到用户程序,而不是再发起一次read调用,这样在完成到用户态的回调后,用户态就可以继续执行接下来的程序了,而不是一直等待,也就完成了异步操作:
至此,关于IO模型的讲解就已经全部结束了,希望对你有所帮助!!!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。