本文阅读大约需要10分钟。
“最近笔者在研究微服务网关,准备着手写一个服务转发的微服务网关进行数据代理与转发,从而更好的服务自己的工程服务。参考了 nginx 、 kafka 、redis 网络模型、 Linux 系统,发现真绕不开io 复用的模型”
系统只能选用Linux酱,其实IO模型有很多种。
阻塞I/O模型
非阻塞I/O模型
I/O复用模型
信号驱动I/O模型
异步I/O模型
今天单纯的来讲 I/O复用模型
打个比方:思考一个问题,我们平时工作的时候,一般是几个人一起干(线程),但如果承载不了目前快速发展的业务的话,是不是得增加人(线程数量)?我们都知道雇人要花大量的钱(消耗大量资源),这样会造成支出大于成本破产(系统资源不足)
那有没有一种方式可以让,让一个人(线程)服务多个上下游(客户端)对接?
有复用(IO复用技术),就是对上面的事情最好的解答。
对于IO复用,我们通过一个例子来进行讲解 —— 大学时代“追女孩例子”来更好的理解它。
讲个段子:
假设你追女孩子,你的女神住在24楼的某一间,而门口的阿姨是不让男生进楼的。你小鹿乱撞,为了多见一面你的女神,在不违法的情况下,只能在门口一直等啊等,对每一个女孩子都要仔细的观察👀 ,生怕错过。 但是你也是会困,会渴,会累。怎么办呢?不能错过女神,较劲脑汁的你,想到了肯定不是我一个人喜欢,大家一起来盯女神,相互告知一下,你们约定好在24楼门口一起盯喜欢的女神。如果把以上的男孩子当作服务器,把女孩子比做客户端,那么这就是一个线程的例子。
后来有一天,你想啊——“我想高效见到我的女神”,她出现在门口的时候,就只要自己偷偷的(不告诉其他人)去陪着她。你就想啊想,贿赂一下宿管阿姨,让她帮你盯着,看到你的女神,就通过wechat告诉你,此时你就放下手上的事,兴致冲冲的去24楼找你的女神。现在这种情况就是一个IO复用的情况。
目前常见的IO复用模型存在三种分别是 select 、 poll 、epoll。
你追女神模式之-select
当你采用这种模式追女神的时候,有女孩进出宿舍(i/o事件),你不知道哪个是你的女神,你只能无差别的挨个观察👀所有女孩子,你很聪明,将每一个女孩都拍照(数组存储有上限)仔细比对,然后找出你的你女神,跟她聊天。所以你的时间复杂度很差效率很低o(n)同时这个门口人流量越大,你无差别挨个观察时间越长。
你追女神模式之-poll
当你采用这个模式追你的女神的时候,你将拍照的数据做了位置转移(从你的手机实时导入到你的电脑中—— 数组拷贝到内核空间 从数组转换为链表,没有了存储限制 ),然后聪明的你用上了人工比对图像( 查询每个fd对应的状态 ),本质上你还是在如同上面的效率o(n)
你追女神模式之-epoll( event poll )
不同于select、poll 两种模式,这个时候宿管阿姨很重要,她帮助你监视你的女神「你的女神可以有多个」(事件)的行动( 哪个流发生了怎样的I/O事件 ),阿姨会给拍照一张你女神的照片,发送到你的电脑上( 每个事件关联上fd )。此时你接收到宿管阿姨的消息,就立刻向24楼宿舍冲去。此时时间复杂度就降低到了o(1)
小结
Select 、 poll 、 epoll 都是IO多路复用的机制。IO多路复用就是通过一种机制,可以监视多个女神(描述符),一旦你的女神出现(某个资源描述符达到读/写就绪状态)就立刻通知你,然后你就根据你的女神的偏好进行后续一系列追求攻势(通知其他程式进行读写操作),但本质上select poll epoll 都是同步操作I/O操作,算是有始有终,比较专一的(也就是说他们只对自己关注的读写状态,负责读写操作。)整个过程不能并行(每个资源需要同步阻塞)
那有没有渣男(一种高效的程序)?很高兴,告诉你有的。比如自己惹完的姑娘,通过各种合规操作(异步I/O),交给一个多人格来进行处理(异步I/O的实现会负责把数据从内核拷贝到用户空间)。
好了至此段子算是讲完了。
思考
一个问题,操作系统会有什么运行方式来支持IO多路复用呢? epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现。
同步阻塞(BIO)
- 服务端采用单线程,当accept一个请求后,在recv或send调用阻塞时,将无法accept其他请求(必须等上一个请求处recv或send完),
无法处理并发
- 服务器端采用多线程,当accept一个请求后,开启线程进行recv,可以完成并发处理,但随着请求数增加需要增加系统线程,
大量的线程占用很大的内存空间,并且线程切换会带来很大的开销,10000个线程真正发生读写事件的线程数不会超过20%,每次accept都开一个线程也是一种资源浪费
同步非阻塞(NIO)
- 服务器端当accept一个请求后,加入fds(fd全称是file descriptor)集合,每次轮询一遍fds集合recv(非阻塞)数据,没有数据则立即返回错误,
每次轮询所有fd(包括没有发生读写事件的fd)会很浪费cpu
IO多路复用
- 服务器端采用单线程通过select/epoll等系统调用获取fd列表,遍历有事件的fd进行accept/recv/send,使其能
支持更多的并发连接请求
深入技术:
select:
select是第一个实现 (1983 左右在BSD里面实现的)select 被实现以后,很快就暴露出了很多问题
- select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的,说白了就是线程不安全造成的。
- select 如果I/O 出现了数据,仅仅只会返回,并不会告诉你是谁上面返回的数据,这个时候你要想知道是谁,就得遍历一次,几十个无所谓,几百个就很鸡肋
- 很遗憾select 只能监听1024/2048个链接,这是系统默认规定的(能改)
- select 是线程不安全的,这点上面说过,sock不用,恭喜你,我拒绝,我不高兴不准回收。你可以不用我,但是我一定给你搞一个烂摊子。
“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
poll:
后来对select 做改进,搞了一个poll出来,多少链接数量,您说了算,高兴就好。
存储从原来的数组改为了链表。
还支持水平触发。没有被处理,那么下次poll时会再次报告该fd。
时代在发展,社会在进步,计算机处理效率越来越高。原有的设计在现在看来,较着有点落后。1千个链接数量在那个年代很合适,但是到如今,就显得不能满足当下的需求了。 poll 依然保留了select 致命的缺点,线程不安全。
epoll:
2002年 (性能危机的那几年) 大神 Davide Libenzi 实现了epoll。 现在我是安全的了, 我不仅今天能告诉你是哪个sock内部数据,还会告诉你哪个sock 有数据了,你都不用去找了,多好。
这是一张压测图,详细对比了poll epoll性能,发现当连接发送数量随着请链接增加,从而处理速度下降这种事情发生。
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。
默认模式下:只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作。 高速(边缘触发)模式下:它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。
所以在高速模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。
epoll 设计优势 消息传递 (类似于go的两极线程模型-打通用户和内核两态) epoll通过内核和用户空间共享一块内存来实现的。 存储开销小 10万左右的sock 大约1GB内存开销 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递
总结一下:
select则应该是POSIX所规定 ,一般情况下,操作系统均有实现。
select 不是线程安全的,
select 只能监视1024个链接
select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找
select 会修改传入的参数数组,这个对于一个需要调用很多次的函数
poll 和 select 比较像,然而它并没有解决高效(时间、资源浪费)的问题,所以被后来演进的epoll模型所取代。
poll 去掉了1024个链接的限制,于是要多少链接呢
poll 从设计上来说,不再修改传入数组,
但是poll仍然不是线程安全的
epoll是Linux所特有,macOS系统有个特别有意思的实现 kqueue 来实现类epoll模型
epoll 现在是线程安全的。
epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。
其实当你的请求量真的不是很大的时候没有必要上epoll 模型,但是你要设计一个高性能的服务转发网关的时候(有点追求吧)你需要用到,就像潘剑锋的GNET一样。
最后
想起来 靓坤 的一句话:你知道 Nginx、Kafka、Redis 系统高IO操作的服务都是IO复用的模型吗?路还很长,慢慢看、慢慢学吧, 还依稀的记得身边的同事常说用epoll模型追妹子,感慨他的🐮。
参考:操作系统——IO多路复用原理分析 - 庄小焱
参考:Nginx官网
参考:工业出版社-计算机操作系统