Mycat 网络通信模块源码
Mycat 用户手册:
系统I/O 可分为阻塞型, 非阻塞同步型以及非阻塞异步型.
阻塞型I/O意味着控制权直到调用操作结束了才会回到调用者手里. 结果调用者被阻塞了, 这段时间了做不了任何其它事情. 更郁闷的是,在等待IO结果的时间里,调用者所在线程此时无法腾出手来去响应其它的请求,这真是太浪费资源了。拿read()操作来说吧, 调用此函数的代码会一直僵在此处直至它所读的socket缓存中有数据到来.
相比之下,非阻塞同步是会立即返回控制权给调用者的。调用者不需要等等,它从调用的函数获取两种结果:要么此次调用成功进行了;要么系统返回错误标识告诉调用者当前资源不可用,你再等等或者再试度看吧。比如read()操作, 如果当前socket无数据可读,则立即返回EWOULBLOCK/EAGAIN,告诉调用read()者“数据还没准备好,你稍后再试”。
在非阻塞异步调用中,稍有不同。调用函数在立即返回时,还告诉调用者,这次请求已经开始了。系统会使用另外的资源或者线程来完成这次调用操作,并在完成的时候知会调用者(比如通过回调函数)。拿Windows的ReadFile()或者POSIX的aio_read()来说,调用它之后,函数立即返回,操作系统在后台同时开始读操作。
在以上三种IO形式中,理论上,非阻塞异步是性能最高、伸缩性最好的。
同步和异步是相对于应用和内核的交互方式而言的,同步需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。
对于JAVA的API来说:
- java.net.Socket就是典型的阻塞型IO
- java NIO非阻塞同步
- java AIO非阻塞异步
MyCAT起源于Cobar,Cobar前端为NIO后端为BIO,后端就是通过java.net.Socket进行读写,所以Cobar后端每次进行读写都会造成线程阻塞,后端能支持的连接总数就成为瓶颈所在。
MyCAT在基于Cobar改版时,直接采用了Java 7的AIO,前后端都实现了非阻塞异步。由于Linux并没有真正实现AIO,实际测试下来,AIO并不比NIO快,反而性能上比NIO还要慢。所以MyCAT在2014年下半年,做了一次网络通信框架的大调整,改为同时支持AIO和NIO,通过启动参数让用户来选择哪种方式。虽然现在AIO比NIO慢,但是MyCAT仍然保留了AIO实现,就是为了等Linux真正实现AIO后,可以直接支持。
Reactor和Proactor
MyCAT同时实现了NIO和AIO,为了便于读者更清楚理解代码实现,先介绍NIO和AIO分布对应的两种设计模式:Reactor和Proactor
一般情况下,I/O 复用机制需要事件分享器(event demultBossiplexor). 事件分享器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁的什么东西送了, 快来拿吧。开发人员在开始的时候需要在分享器那里注册感兴趣的事件,并提供相应的处理者(event handlers),或者是回调函数; 事件分享器在适当的时候会将请求的事件分发给这些handler或者回调函数。
涉及到事件分享器的两种模式称为:Reactor和Proactor. Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的. 在Reactor模式中,事件分离者等待某个事件或者应用或操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分离者就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
而在Proactor模式中,事件处理者(或者代由事件分离者发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区,读的数据大小,或者用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分离者得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称有overlapped的技术),事件分离者等IOCompletion事件完成. 这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。
Reactor与Proactor两种模式的场景区别:
下面是Reactor的做法:- 等待事件响应 (Reactor job)
- 分发 “Ready-to-Read” 事件给用户句柄 ( Reactor job)
- 读数据 (user handler job)
- 处理数据( user handler job)
下面再来看看真正意义的异步模式Proactor是如何做的: - 等待事件响应 (Proactor job)
- 读数据 (Proactor job)
- 分发 “Read-Completed” 事件给用户句柄 (Proactor job)
- 处理数据(user handler job)
从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.
最后结合下面的两张图更容易理解(这是别人的图,非原创):\
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341