Mycat开发实践---Mycat的网络通信框架
Posted wangshuang1631
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mycat开发实践---Mycat的网络通信框架相关的知识,希望对你有一定的参考价值。
1从一个测试说起
网上有人对Cobar和MyCAT做了一个简单的比较测试,过程如下:
1 测试环境
利用A、B、C三大类服务器,在A台上面安装配置MyCAT及Cobar,这样保证了硬件方面的一致性。B类服务器上安装Apache这一web服务,使用php语言。C类安装mysql数据库,其中B类与C类均不止一台,主要目的是为了作压力的均分。C类服务器安装了4台,存放了相同的数据库,对其中一个表进行分片存储。
测试软件使用的是loadRunner。在对两个中间件分别进行测试的过程中,采用的web服务器执行页面及相关数据库,均未调整,仅在中间件上有分别。比对情况如下图:
表格中场景状态下,明显MyCAT通过事务达到27544个,而Cobar只有2998,原因应该是Cobar假死之后对相关请求处理,均不再响应。
另外Cobar的内存直接上到300000KB以上,手动使用页面对测试实例连接单独访问访问不了,涉及到测试表的所有操作均不能再操作。Cobar内部使用show cobar_status;命令回馈正常。但是使用show cobar_cluster;命令,cobar反馈不了cobar的节点信息,而是返回empty set。
测试过程中MyCAT行为正常。
Cobar存在上述致命问题的原因是后端采用了BIO,每个请求在等待应答时都会占用一个线程,当前端并发量大时,就产生了假死的现象。
MyCAT对Cobar的网络框架进行了重构,后端BIO改为为AIO和NIO,同时还做了其它方面的优化,下面就慢慢道来~~~
2 MyCAT网络框架
2.1 三种IO类型
系统I/O 可分为阻塞型, 非阻塞同步型(NIO)以及非阻塞异步型(AIO).
阻塞型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做了一次网络通信框架的大调整,改为同时支持AIO和NIO,通过启动参数让用户来选择哪种方式。虽然现在AIO比NIO慢,但是MyCAT仍然保留了AIO实现,就是为了等Linux真正实现AIO后,可以直接支持。
2.2 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设备.
最后结合下面的两张图更容易理解:
可以看到两者图中都有分离器,在JAVA NIO框架中分离器的逻辑需要用户通过selector自己完成。在JAVA AIO框架中,分离器有系统API自动完成,AsynchronousChannelGroup就代替了分离的作用。
2.3 支持AIO和NIO的框架
前面已经讲了,MyCAT可以通过系统参数选择是使用AIO还是NIO,那么在代码里面是如何做到同时支持两种架构的呢。可以看下面的类图:
- SocketConnector 发起连接请求类,如MyCAT与MySQL数据库的连接,都是由MyCAT主动发起连接请求
- SocketAcceptor 接收连接请求类,如MyCAT启动9066和8066分别侦听管理员和应用程序的连接请求
- SocketWR读写操作类,SocketConnector和SocketAcceptor只负责socket建立,当socket连接建立后进行字节的读写操作则由SocketWR来完成。
这几个接口分别处理网络通道的四种不同类型的事件:
- Connect客户端连接服务端事件
- Accept 服务端接收客户端连接事件
- Read 读事件
- Write 写事件
NIO主要类调用
AIO主要类调用-服务端
AIO主要类调用-客户端
看起来好像是AIO的调用比NIO多吧,其实NIO比AIO要略麻烦些,因为AIO的调用关系全画了,NIO对链接建立过程进行简化,否则一个图上画不完。
2.4 MyCAT的NIO实现
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
Selector可以监听四种不同类型的事件:
- Connect
- Accept
- Read
- Write
这四种事件用SelectionKey的四个常量来表示:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
前面已经说了,NIO采用的Reactor模式:例如汽车是乘客访问的主体(Reactor),乘客上车后,到售票员(acceptor)处登记,之后乘客便可以休息睡觉去了,当到达乘客所要到达的目的地后,售票员将其唤醒即可。
典型的Reactor场景:
在高性能IO框架中,大都是采用多Reactor模式,即多个dispatcher,如下图所示:
上图是服务端采用多Reactor模式的典型场景,MyCAT也采用多Reactor模式,另外MyCAT不仅做服务端,也要作为客户端去连接后端MySQL Server,所以实际场景如下图所示:
多Reactor区分说明:
通常Reactor实现为一个线程,内部维护一个Selector
3 与Cobar原有NIO细节比较
3.1 Cobar的NIO
Cobar后端是采用BIO,前端采用NIO;Cobar的BIO这儿就不必提了,对于原有NIO实现,跟MyCAT相比,读方式差不多,写的差别比较大。
NIOReactor.postWrite()
这儿传入的参数,不是要写的buffer,而是一个连接对象,只是注册这个对象有内容需要写。要写的buffer,在连接对象自己的缓存队列中
这种方式与MyCAT差不多,连接对象自己维护写队列。
NIOReactor.W内部类
专门负责缓冲队列写,不停循环遍历,等待其它业务线程放入写数据
NIOReactor.R内部类
为一个seletor同时处理读事件和写事件。但是主要负责的是读,只有在网络非常繁忙等极少数情况下,小概率走到读分支。
基于队列的写和基于事件的写
- 队列写:所有的写请求,放到缓存队列,由独立W线程进行写。如果未写完(比如网络繁忙),则注册写事件,然后会再seleltor发现写事件
- 事件写:R线程中,seletor探测到写事件后,进行写操作。如果写完了,则立即取消注册写事件,避免继续触发导致循环
- 总结:主要是W线程进行写,只有在网络繁忙时,才会注册写事件,等待网络写就绪后,R线程就会立即发现写事件,然后R线程再写一部分。
3.2 比较MyCAT和Cobar两种写方式
Cobar的写:业务线程把写请求放到缓冲队列,然后由独立写线程W负责,当W在写的时候,网络慢等原因导致未写完,然后注册写事件,由R线程(selector)进行候补写
MyCAT的写:业务线程先通过加锁或者AtomicBoolean判断当前channel是否正在写数据,如空闲则由当前线程直接写,否则入缓冲队列交给其他线程写;在写的时候,网络慢等原因导致未写完,
然后注册写事件,由NIOReactor线程(selector)进行候补写;MyCAT采用这种方式的显著优点:尽可能减少系统调用和线程切换;
4 MyCAT的AIO实现
4.1 JAVA AIO体系
从代码风格上比较,NIO和AIO的差别,就是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)
可以看到两者最大的区别,就是到了AIO,用户只管专心负责对读到的数据进行处理,如何读的过程过程就全交给系统层面去完成。
同样对于写操作,在AIO方式中,应用层只管把要写的buffer传递出去,等到系统写完,再回调应用层做其它动作。
而在NIO方式中,应用层要自己控制buffer写入channel的过程。
首先看下AIO引入的新的类和接口:
java.nio.channels.AsynchronousChannel
标记一个channel支持异步IO操作。
java.nio.channels.AsynchronousServerSocketChannel
ServerSocket的aio版本,创建TCP服务端,绑定地址,监听端口等。 java.nio.channels.AsynchronousSocketChannel
面向流的异步socket channel,表示一个连接。 java.nio.channels.AsynchronousChannelGroup
异步channel的分组管理,目的是为了资源共享。一个AsynchronousChannelGroup绑定一个线程池,这个线程池执行两个任务:处理IO事件和派发CompletionHandler。AsynchronousServerSocketChannel创建的时候可以传入一个 AsynchronousChannelGroup,那么通过AsynchronousServerSocketChannel创建的 AsynchronousSocketChannel将同属于一个组,共享资源。 java.nio.channels.CompletionHandler
异步IO操作结果的回调接口,用于定义在IO操作完成后所作的回调工作。
AIO的API允许两种方式来处理异步操作的结果:返回的Future模式或者注册CompletionHandler,MyCAT采用的是CompletionHandler的方式,这些handler的调用是由 AsynchronousChannelGroup的线程池派发的。
以上是关于Mycat开发实践---Mycat的网络通信框架的主要内容,如果未能解决你的问题,请参考以下文章