大神Doug Lea在http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf 里详细介绍了java nio的实现思路,里面也详细介绍了reactor模式,后文统一称为老爷子的文章。
看这篇文章建议先了解下linux中的文件描述符与套接字socket redis中的IO多路复用select和epoll
传统阻塞I/O模型
以上图片摘自大神Doug lea 的nio。
特点
-
一个后端线程只能处理一个客户端请求
-
采用I/O阻塞的模式处理客户端请求
分析
-
随着并发量上升,后端资源有限的情况下会占用大量的线程资源;
-
如果后端资源有限,那么后面来的客户端请求会阻塞;
-
线程的创建和销毁也会占用大量的cpu资源;
改进
- 可以引入线程池,减少线程的重复创建和销毁
reactor模式
在https://en.wikipedia.org/wiki/Reactor_pattern中。
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers
梳理下关键点:
-
是一种事件驱动处理模式
-
处理一个或多个输入并发请求
-
服务器通过handler对请求进行多路分发,分发给request handlers
在上一篇中讲了I/O多路复用,
在老爷子的文章里对reactor pattern有如下说明:
-
reactor 通过分发IO事件给对应的handler处理;
-
handlers执行非阻塞操作
-
通过绑定处理器handlers处理对应的事件进行管理(这个就是acceptor)
然后老爷子又分了三种reactor模式
单线程reactor模型,也是最基本的reactor模式
java nio是这么支持的
-
channels 连接到支持非阻塞读取的文件、socket等
-
buffers 可以被channels读取或写入的缓冲区
-
selectors 识别出哪个channels中有io事件
-
selectionKey io事件状态和处理器的绑定
老爷子在文档里也举了一个例子,这里不再说明。
-
这个模式里,reactor负责分发(dispatch)
-
然后由启动Acceptor调用对应的Handler
-
由Handler进行网络io读、业务处理,网络io写;
redis5.0之前都是使用类似这种模式;
-
aeMain进行loop
-
aeProcessEvents进行事件捕获与分发
-
通过aeApiPoll 捕获事件
-
通过事件类型掩码mask进行事件处理(分发即处理)
-
通过fe->rfileProc和fe->wfileProc 执行然后又重新注册新的事件
单reactor多线程处理
-
在这个模式里reactor只负责分发
-
业务逻辑都交给了线程池处理
在这个模式里reactor还是单线程,不过reactor获取到的任务会分发给线程池处理(redis中实现时,主线程也是一条工作线程)
redis6.0使用这种模式的变种
-
aeMain进行loop
-
aeProcessEvents进行事件捕获与分发
-
通过fe->rfileProc和fe->wfileProc 执行然后又重新注册新的事件
-
通过aeApiPoll 捕获事件(就是上一篇讲的IO多路复用机制)
-
捕获后分发给线程池(轮询分发)
-
线程池处理分发的任务(主线程是线程池第一个线程)
-
通过fe->rfileProc和fe->wfileProc 执行然后又重新注册新的事件
-
主线程等待所有的线程处理完,然后进入下一个轮询
多reactor多线程处理
nginx应该是使用这种模式
最后奉上之前画的io处理流程图
redis系列文章
redis源码阅读四-我把redis6里的io多线程执行流程梳理明白了
redis源码阅读五-为什么大量过期key会阻塞redis?
本文是Redis源码剖析系列博文,有想深入学习Redis的同学,欢迎star和关注;
Redis中文注解版:https://github.com/yxkong/redis/tree/5.0
如果觉得本文对你有用,欢迎一键三连;
同时可以关注微信公众号5ycode获取第一时间的更新哦;