Java 远程通讯_MINA
- 格式:docx
- 大小:4.19 MB
- 文档页数:37
第一章MINA前述1.1线程模型MINA线程模型采用了Reactors in threads模型,即Main Reactor + Sub Reactors的模式。
由main reactor处理连接相关的任务:accept、connect等,当连接处理完毕并建立一个socket连接(称之为session)后,给每个session分配一个sub reactor,之后该session的所有IO、业务逻辑处理均交给了该sub reactor。
每个reactor均是一个线程,sub reactor中只靠内核调度,没有任何通信且互不打扰。
现在来讲讲我对线程模型演进的一些理解:Thread per Connection:在没有nio之前,这是传统的java网络编程方案所采用的线程模型。
即有一个主循环,socket.accept阻塞等待,当建立连接后,创建新的线程/从线程池中取一个,把该socket连接交由新线程全权处理。
这种方案优缺点都很明显,优点即实现简单,缺点则是方案的伸缩性受到线程数的限制。
Reactor in Single Thread:有了nio后,可以采用IO多路复用机制了。
我们抽取出一个单线程版的reactor模型,时序图见下文,该方案只有一个线程,所有的socket连接均注册在了该reactor上,由一个线程全权负责所有的任务。
它实现简单,且不受线程数的限制。
这种方案受限于使用场景,仅适合于IO密集的应用,不太适合CPU密集的应用,且适合于CPU资源紧张的应用上。
Reactor + Thread Pool:方案2由于受限于使用场景,但为了可以更充分的使用CPU资源,抽取出一个逻辑处理线程池。
reactor仅负责IO任务,线程池负责所有其它逻辑的处理。
虽然该方案可以充分利用CPU资源,但是这个方案多了进出thread pool的两次上下文切换。
Reactors in threads: 基于方案3缺点的考虑,将reactor分成两个部分。
main reactor负责连接任务(accept、connect等),sub reactor负责IO、逻辑任务,即mina与netty的线程模型。
该方案适应性十分强,可以调整sub reactor的数量适应CPU资源紧张的应用;同时CPU密集型任务时,又可以在业务处理逻辑中将任务交由线程池处理,如方案5。
该方案有一个不太明显的缺点,即session没有分优先级,所有session平等对待均分到所有的线程中,这样可能会导致优先级低耗资源的session堵塞高优先级的session,但似乎netty与mina并没有针对这个做优化。
Reactors in threads + Threads pool: 这也是我所在公司应用框架采用的模型,可以更为灵活的适应所有的应用场景:调整reactor数量、调整thread pool大小等。
1.2 任务粒度任务从逻辑上我给它分为成三种类型:连接相关的任务(bind、connect等)、写任务(write、flush)、调度任务(延迟、定时等),读任务则由selector加循环时间控制了。
mina任务调度的趋势是逐渐变小,从session级别的调度 -> 类型级别任务的调度 -> 任务的调度。
mina-1.1.7: SocketIoProcessor$Worker.runmina-2.0.4: AbstractPollingIoProcessor$Processor.runmina-3.0.0.M3-SNAPSHOT: AbstractNioSession.processWritemina1、2的任务调度粒度为session。
mina会将有IO任务的的session写入队列中,当循环执行任务时,则会轮询所有的session,并依次把session中的所有任务取出来运行。
这样粗粒度的调度是不公平调度,会导致某些请求的延迟很高。
mina3的模型改动比较大,代码相对就比较难看了,我仅是随便扫了一下,它仅提炼出writeQueue。
1.3 数据如何Read/writejava nio如果是non-blocking的话,在每次write(bytes[N])的时候,并不会将N字节全部write出去,每次write仅一部分(具体大小和tcp_write_buffer有关)。
那么,mina是怎么处理这种情况的呢?mina-1.1.7: SocketIoProcessor.doFlushmina-2.0.4: AbstractPollingIoProcessor.flushNowmina-3.0.0.M3-SNAPSHOT: AbstractNioSession.processWritemina1、2的方式基本一致。
在发送端每个session均有一个writeBufferQueue,有这样一个队列,可以保证写入与写出均有序。
在真正write时,大致逻辑均是一一将队列中的writeBuffer取出,写入socket,但有一些不同的是,mina1是每次peek一次,当该buffer全部写出之后再poll(mina3也是这种机制);而mina2则是直接poll第一个,将其存为currentWriteRequest,直到currentWriteRequest全部写出之后,才会poll下一个。
这样的做法是为了省几次peek的时间么?同时mina在write时,有一种spin write的机制,即循环write多次。
mina1的spin write count为256,写死在代码里了,表示256有点大;mina2这个机制废除但代码保留;每次write只是输出了一部分数据,read同理,也有可能只会读入部分数据,这样就是导致读入的数据是残缺的。
而mina默认不会理会这种由于nio导致的数据分片,需要由业务层自己额外做配置或者处理。
nfs-rpc: ProtocolUtils.decodemina-1.1.7: SocketIoProcessor.read, CumulativeProtocolDecoder.decodemina-2.0.4: AbstractPollingIoProcessor.read,CumulativeProtocolDecoder.decodemina-3.0.0.M3-SNAPSHOT: NioSelectorLoop.readBuffernfs-rpc在协议反序列化的过程中,就会考虑这个的问题,依次读入每个字节,当发现当前字节或者剩余字节数不够时,会将buf的readerIndex设置为初始状态。
具体的实现,有兴趣的同学可以学习nfs-rpc:ProtocolUtils.decodenfs-rpc在decode时,出现错误就会将buf的readerIndex设为0,把readerIndex设置为0就必须要有个前提假设:每次decode时buf是同一个,即该buf是复用的。
那么,具体情况是怎样呢?mina1、2的读buffer创建方式比较土,在每次read之前,会重新allocate一个新的buf对象,该buf 对象的大小是根据读入数据大小动态调整。
当本次读入数据等于该buf大小,下一次allocate的buf对象大小会翻倍;当本次读入数据不足该buf大小的二分之一,下一次allocate的buf对象同样会缩小至一半。
需要注意的是,*2与/2的代码都可以用位运算,但是mina1竟没用位运算,有意思。
mina1、2处理数据分片可以继承CumulativeProtocolDecoder,该decoder会在session中存入(BUFFER, cumulativeBuffer)。
decode过程为:1)先将message追加至cumulativeBuffer;2)调用具体的decode逻辑;3)判断cumulativeBuffer.hasRemaining(),为true则压缩cumulativeBuffer,为false则直接删除(BUFFER, cumulativeBuffer)。
实现业务的decode逻辑可以参考nfs-rpc中MinaProtocolDecoder的代码。
mina3在处理读buffer的创建与数据分片比较巧妙,它所有的读buffer共用一个buffer对象(默认64kb),每次均会将读入的数据追加至该buffer中,这样即省去了buffer的创建与销毁事件,也省去了cumulativeDecoder的处理逻辑,让代码很清爽啊!ByteBuffer设计需要说明的是,只有mina1、2才有自己的buffer类,mina3内部只用nio的原生ByteBuffer类(提供了一个组合buffer的代理类-IoBuffer)。
mina1、2自建buffer的原因如下:It doesn’t provide useful getters and putters such as fill,get/putString, andget/putAsciiInt()enough.It is difficult to write variable-length data due to its fixed capacity第一条比较好理解,即提供了更为方便的方法用以操作buffer。
第二条则是觉得nio的ByteBuffer是定长的,无法自动扩容或者缩容,所以提供了自动扩/缩容的方法:IoBuffer.setAutoExpand,IoBuffer.setAutoShrink。
但是扩/缩容的实现,也是基于nio的ByteBuffer,重新ByteBuffer.allocate(capacity),再把原有的数据拷贝过去。
第二章MINA框架与应用Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于 TCP/IP、UDP/IP协议栈的通信框架(当然,也可以提供 JAVA 对象的序列化服务、虚拟机管道通信服务等),Mina 可以帮助我们快速开发高性能、高扩展性的网络通信应用,Mina 提供了事件驱动、异步(Mina 的异步 IO默认使用的是 JAVA NIO 作为底层支持)操作的编程模型。
从官网文档“MINA based Application Architecture”中可以看到Mina作为一个通信层框架,在实际应用所处的位置,如图所示:Mina位于用户应用程序和底层Java网络API(和in-VM通信)之间,我们开发基于Mina的网络应用程序,就无需关心复杂的通信细节。
再看一下,Mina架构,如图所示:通信过程:1.通过SocketConnector同服务器端建立连接2.链接建立之后I/O的读写交给了I/O Processor线程,I/O Processor是多线程的3.通过I/O Processor读取的数据经过IoFilterChain里所有配置的IoFilter,IoFilter进行消息的过滤,格式的转换,在这个层面可以制定一些自定义的协议4.最后IoFilter将数据交给Handler进行业务处理,完成了整个读取的过程5.写入过程也是类似,只是刚好倒过来,通过IoSession.write写出数据,然后Handler进行写入的业务处理,处理完成后交给IoFilterChain,进行消息过滤和协议的转换,最后通过I/O Processor将数据写出到socket通道IoFilterChain作为消息过滤链1.读取的时候是从低级协议到高级协议的过程,一般来说从byte字节逐渐转换成业务对象的过程2.写入的时候一般是从业务对象到字节byte的过程IoSession贯穿整个通信过程的始终MINA2工作流程:一个Acceptor线程有一个自己的Selector,这个Selector专门监听端口上的connect事件。