Redis之IO线程、IO多路复⽤,BIO、NIO和AIO区别
redis的线程
redis是单线程操作的,但是却可以处理⾼并发。原因是基于多路复⽤的⾮阻塞IO,基于NIO(non_blocking_io);
redis为什么这么快?
完全基于内存,绝⼤部分请求是纯粹的内存操作;
数据结构简单,对数据操作也简单,redis中的数据结构是专门进⾏设计的;
采⽤单线程,避免了不必要的上下⽂切换和竞争条件,不⽤考虑加锁释放锁和死锁的问题;
使⽤多路复⽤模型,⾮阻塞IO;
对多路复⽤的理解
多路Io复⽤是利⽤select、poll、epoll可以同时监察多个流的IO事件的能⼒,在空闲的时候会把当前线程阻塞,当有⼀个或多个流由IO事件发⽣时,就从阻塞态中唤醒,于是程序就会轮询⼀遍所有的流(epoll只
是轮询那些真正发出了时间的流),并且⼀次顺序的处理就绪的流。
select:服务端⼀直在轮询、监听如果有客户端链接上来就创建⼀个连接放到数组A中,继续轮询这个数组,如果在轮询的过程中有客户端发⽣IO事件就去处理;select只能监视1024个连接(⼀个进程只能创建1024个⽂件);⽽且存在线程安全问题;
深⼊理解:
poll:在select做了许多修复,⽐如不限制监测的连接数;但是也有线程安全问题;
epoll:也是监测IO事件,但是如果发⽣Io事件,它会告诉你是哪个连接发⽣了事件,就不⽤再轮询访问。⽽且它是线程安全的,但是只有linux平台⽀持;
IO多路复⽤(Reactor)
实际上虽然上述⽅式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的,平均时间甚⾄⽐同步阻塞IO模型还要长,⽽IO多路复⽤模型使⽤了Reactor设计模式实现线程不必阻塞,可以⼲别的事情,当IO事件触发时,再进⾏处理。
Reactor设计模式
EventHandler表⽰IO事件处理器,它拥有IO⽂件句柄Handle以及对Handle的操作handle_event。Reactor⽤于管理EventHandler进⾏注册删除,并使⽤handle_events实现事件循环,不断调⽤同步时间多路分离器的多路分离函数select,只要某个⽂件句柄被激活,select就返回(阻塞),handle_events就会与⽂件句柄关联的事件处理器的handle_event进⾏相关操作。
将⽤户线程轮询的IO操作状态的⼯作统⼀交给handle_events事件循环进⾏处理,⽤户线程注册事件处理器之后可以继续执⾏其他⼯作,⽽Reactor线程负责返回select函数检查socket状态,当有socket被激活⽯,则通知相应⽤户线程执⾏handle_event进⾏数据读取处理⼯作。
如何结合事件模型使⽤NIO同步⾮阻塞特性
在NIO中如果⼀个连接不能读写,我们可以把这件事记下来,记录⽅式是在Selector上注册标记位,然后切换到其他就绪的连接继续进⾏读写。
NIO中的主要事件: 读就绪、写就绪、有新的连接到来;
我们⾸先需要注册当这⼏个时间到来的时候所对应的处理器,然后在合适的时机告诉事件选择器:我们对这个事件感兴趣。
对于写操作,就是写不出去的时候对写事件感兴趣;
对于读操作,就是完成连接和系统没有办法承载新读⼊的数据时;
对于accept,⼀般是服务器刚启动时;
对于connect,⼀般是connect失败需要重新连接或者直接异步调⽤时;
对BIO、NIO和AIO的概念解析
所有的系统IO都分为两个阶段:等待就绪和操作。例如:读函数分为等待系统可读和真正的读;同理,写函数分为等待⽹卡可写和真正的写。
可以将这三种IO的特点概括为:
BIO⾥⽤户最关⼼"我要读";我要⼀直等在这处理处理时间;
NIO⾥⽤户最关⼼"我可以读了";我不在这等了,如果有事件发⽣,你就通知我,我再来处理;
AIO⾥⽤户最关⼼"读完了";你帮我处理吧,处理完了通知我。
BIO
Java BIO : 同步并阻塞,服务器实现模式为⼀个连接⼀个线程,即客户端有连接请求时服务器端就需要启动⼀个线程进⾏处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
每⼀个线程守着⼀个IO通道,当没有IO时这个线程就阻塞着,⼀旦有IO事件发⽣,这个线程就开始⼯作
缺点
严重依赖线程:
线程的创建和销毁成本很⾼;
线程本⾝占⽤较⼤内存;
线程切换成本很⾼;
容易造成锯齿状的系统负载;
Java NIO : 同步⾮阻塞,服务器实现模式为⼀个请求⼀个线程,即客户端发送的连接请求都会注册
到多路复⽤器上,多路复⽤器轮询到连接有I/O请求时才启动⼀个线程进⾏处理。socket主要的读、写、注册和接受函数,在等待就绪阶段都是⾮阻塞的,真正的IO操作是同步阻塞的。
将每个连接(IO通道)都注册到Selector多路复⽤器上,告诉复⽤器我已经连接了,如果有IO事件发⽣,你就通知我。Selector就不断地调⽤select函数,去访问每⼀个通道看那个通道有IO事件发⽣,如果发⽣了就通知,内核开启⼀个对应事件的线程去⼯作
Java AIO(NIO.2) : 异步⾮阻塞,服务器实现模式为⼀个有效请求⼀个线程,客户端的I/O请求都是由OS先完成了再通知服务器应⽤去启动线程进⾏处理。当进⾏读写操作时,只须直接调⽤API的read或write⽅法即可。这两种⽅法均为异步的,对于读操作⽽⾔,当有流可读取时,操作系统会将可读的流传⼊read⽅法的缓冲区,并通知应⽤程序;对于写操作⽽⾔,当操作系统将write⽅法传递的流写⼊完毕时,操作系统主动通知应⽤程序。 即可以理解为,read/write⽅法都是异步的,完成后会主动调⽤回调函数。
Reactor和Proactor
Reactor模式是基于同步IO的,⽽Proactor模式是基于异步IO相关的
Reactor
redis支持的数据结构
1. 某个事件处理者宣称它对某个socket上的读事件很感兴趣;
2. 事件分离者等待这个时间的发⽣;
3. 当事件发⽣了,事件分离者被唤醒,负责通知先前那个事件处理者;
4. 事件处理着收到消息,于是去那个socket上读数据了;
5. 如果需要,它将再次宣称对这个socket上的读事件感兴趣,⼀直重复上⾯的步骤。
Proactor
1. 事件处理者直接投递发⼀个写操作;
2. 这个时候,事件处理者根本不关⼼写事件,它只管发这么个请求,它魂牵梦萦的是这个鞋操作的完成事件;
3. 它发个命令不管具体的事情了。只等着⽐⼈帮他搞定的时候给他回个话;
4. 事件分离者等着这个写事件的完成;
5. 当事件分离者默默的等待完成事件到来的同时,操作系统已经开始⼲活了,它从⽬标读取数据,放⼊⽤户提供的缓冲区,最后通知事
件分离者,这个事情我⼲完了;
6. 事件分离者通知之前的事件处理者,你吩咐的事情搞定了;
7. 事件处理者这时会发现想要读的书已经放在它提供的缓冲区了,想怎么处理都⾏;
8. 如果有需要,事件处理者还像之前⼀样发起另⼀个写操作,重复上⾯的步骤。
NIO存在的问题
连接数较⼩的时候,优势不明显;
没有完全屏蔽平台差异,仍然是基于各操作系统的IO系统实现的。建议使⽤成熟的NIO框架,Netty、MINA等。
对阻塞、⾮阻塞、同步、异步的理解
同步:⾃⼰亲⾃(⽤户线程)持银⾏卡取钱;
异步:委托朋友(OS)拿着你的银⾏卡和密码(数据缓冲区地址和⼤⼩)去取钱;
阻塞:ATM排队取款,只能等待;
⾮阻塞: 柜台取款,取个号,然后坐在椅⼦上做其它事,等号⼴播会通知你办理,没到号你就不能去,你可以不断问⼤堂经理排到了没有,⼤堂经理如果说还没到你就不能去(使⽤⾮阻塞IO时,如果不能读写Java调⽤会马上返回,当IO事件分发器会通知可读写时再继续进⾏读写,不断循环直到读写完成)。
⼀个IO操作实际上是被分为两步的,就拿处理⽹络数据来说。第⼀步:发起IO请求,第⼆部实际的IO操作。
阻塞IO和⾮阻塞IO的区别在于第⼀步;发起IO请求线程是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO;否则就是⾮阻塞IO;
同步IO和⾮同步IO的区别在于第⼆步;如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、⾮阻塞IO、IO复⽤都是同步IO;如果不阻塞,⽽是由操作系统帮你做完再将结果返回给你,那么就是异步IO。
当有⼀个read操作发⽣时,会经过以下流程
第⼀步:
1. 通过read系统调⽤想内核发起读请求
2. 内核想硬件发送读指令,并等待读就绪
第⼆步:
3. 内核把将要读取的数据复制到描述符所指向的内核缓冲区中
4. 将数据从内核缓冲区拷贝到⽤户进程空间中
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论