mina 其余建议

  1. IoHandlerjava


MINA的内部实现了一个事件模型,而IoHanlder则是全部事件最终产生响应的位置。每个方法的名字很明确代表该事件的含义。messageReceived是接收客户端消息的事件,咱们应该在这里实现业务逻辑。messageSent是服务器发送消息的事件,通常状况下咱们不会使用它。sessionClosed是客户端断开链接的事件,能够在这里进行一些资源回收等操做。值得留意的是,客户端链接有两个事件,sessionCreated和sessionOpened,二者稍有不一样,sessionCreated是由I/O processor线程触发的,而sessionOpened在其后,由业务线程触发的,因为MINA的I/O processor线程很是少,所以若是咱们真的须要使用sessionCreated,也必须是耗时短的操做,通常状况下,咱们应该把业务初始化的功能放在sessionOpened事件中。git

    细心的读者可能会发现,咱们刚刚的例子继承的是IoHandlerAdapter,IoHandlerAdapter其实就是一个      IoHanlder的空的实现,这样咱们就能够不用重载不感兴趣的事件。spring


2.IoSession数据库


在这里,笔者把IoSession的方法大体分红三类服务器

第一类,链接操做功能。网络

最主要的方法有两个,向客户端发送消息和断开链接。能够看的出,write接受的变量是一个Object,可是实际上应该传入什么类型呢?具体还得看你是否使用了ProtocolCodecFilter(下面会详细介绍),若是使用了ProtocolCodecFilter,那这个message将多是一个String,或者是一个用户定义的JavaBean。默认的状况,message是一个ByteBuffer。ByteBuffer是MINA的一个类,跟java.nio.ByteBuffer类同名,MINA 2.0将会将它改为IoBuffer,以免讨论上的误会。session

另外一个值得留意的是Future类,MINA是一个非阻塞的通讯框架,其中一个明显的体现就是调用IoSession.write方法是不会阻塞的。用户调用了write方法以后,消息内容会发到底层等候发送,至于何时发出,就不得而知了。固然,实际上调用了write以后,数据几乎是马上发出的,这得益与NIO的高性能。可是,若是咱们必须确认了消息发出,而后进行某些处理,咱们就须要使用Future类,如下是一个很常见的代码。多线程

经过调用future.join,程序就会阻塞,直至消息处理结束。咱们还能经过future.isWritten得知消息是否成功发送。架构

在这里,笔者顺便说一个实际使用的发现,消息发送是会自动合并的,简单来讲,若是在很短的时间里,对同一个IoSession进行了两次write操做,客户端有可能只收到一条消息,而这条消息就是服务器发出的两条消息先后接起来。这样的设计能够在高并发的时候节省网络开销,而笔者的实际使用过程当中,效果也至关好。可是若是这样行为会致使客户端工做不正常,你也能够经过参数关闭它。并发

第二类,属性存储操做。

一般来讲,咱们的系统是有用户状态的,咱们就须要在链接上存储用户属性,IoSession的Attribute就是这样一个功能。例如两个链接同时连入服务器,一个链接是用户A,用户ID是13,另外一个链接是用户B,用户ID是14,咱们就能够在用户登陆成功以后,调用IoSession.setAttribute(“login_id”,13),而后在其后的操做中,经过IoSession.getAttribute(“login_id”)得到当前登陆用户ID,并进行相应的操做。简单来讲,就是一个相似HttpSession的功能,固然具体的实现方法不同。

第三类,链接状态。

这里就很少说了,从方法名上咱们就能知道它具体的功能。

IoFilter

过滤器是MINA的一个很重要的功能。IoFilter也是一个接口,可是相对比较复杂,这里就不列举它的方法了。简单来讲IoFilter就像ServletFilter,在事件被IoHandler处理以前或以后进行一些特定的操做,可是它比ServletFilter复杂,能够处理不少种事件,除了包括IoHandler的7个事件之外,还有一些内部的事件能够进行操做。

MINA提供了一些经常使用的IoFilter实现,例若有LoggingFilter(日志功能)、BlacklistFilter(黑名单功能)、CompressionFilter(压缩功能)、SSLFilter(SSL支持),这些过滤器比较简单,经过阅读它们的源代码,可以更进一步理解过滤器的实现。笔者在这里要重点介绍两个过滤器,ProtocolCodecFilter和ExecutorFilter


3.ProtocolCodecFilter


ObjectSerializationCodecFactory是Java Object序列化以后的内容直接跟ByteBuffer互相转化,比较适合两端都是Java的状况使用。TextLineCodecFactory就是String跟ByteBuffer的转化,说白了就是文本,例如你要实现一个SMTP服务器,或者POP服务器,就可使用它。而笔者的实际使用,大多数状况都是使用

TextLineCodecFactory

这里说起一下IoFilter的顺序问题,IoFilter是有加入顺序的,例如,先加入LoggingFilter再加入ProtocolCodecFilter,和先加入ProtocolCodecFilter再加入LoggingFilter的效果是不同的,前者LoggingFilter写入日志的内容是ByteBuffer,然后者写入日志的是转换后具体的类,例如String。实际使用的时候,必定要处理好过滤器的顺序。

自定义ProtocolCodecFilter, 自定义编码解码方式,可现实文件的传输

ExecutorFilter

另外一个重要的过滤器就是ExecutorFilter。这里,我须要先说明一下MINA的线程工做模式,MINA默认是单线程处理全部客户端的消息,也就是说,即便你在一台8CPU的机器上面跑,可能也只用到一个CPU,另外,若是某次消息处理太耗时,就会致使其余消息等待,总体的吞吐量降低。不少朋友抱怨MINA的性能差,实际上是由于他们没有加入ExecutorFilter的缘故。ExecutorFilter设计的很精巧,你们能够仔细阅读一下源代码,它会将同一个链接的消息合并起来按顺序调用,不会出现两个线程同时处理同一个链接的状况。

1.IoAcceptor acceptor = ...;   
2.IoServiceConfig acceptorConfig = acceptor.getDefaultConfig();   
3.acceptorConfig.setThreadModel(ThreadModel.MANUAL);

这里再次说起IoFitler的顺序问题,通常状况下,咱们会将ExecutorFilter放在ProtocolCodecFilter以后,由于咱们不须要多线程地执行ProtocolCodec操做,用单一线程来进行ProtocolCodec性能会比较高,而具体的业务逻辑可能还设计数据库操做,所以更适合放在不一样的线程中运行。


4.优化建议

MINA默认配置的性能并非很高的,部分缘由是MINA目前还保留初期版本的架构,另一个缘由是由于JVM的发展。

1.IoAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());
首先咱们关闭默认的ThreadModel设置 ThreadModel是一个很简单的线程实现,用于IoService。可是它实在太弱,以致于在并发环境产生大量问题。在MINA 2.0中,ThreadModel直接被取消。你应该使用ExecutorFilter来实现线程。

acceptor.getDefaultConfig().getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());

而后咱们增长I/O处理线程

每个Acceptor/Connector都使用一个线程来处理链接,而后把链接发送给I/O processor进行读写操做,咱们只能够修改I/O processor使用的线程数,用如下代码设置 固然是要将ExecutorFilter加入,上文已经很详细地描述了 笔者在开发过程当中,屡次遇到OutOfMemoryError,通过研究以后才发现缘由。MINA默认是使用direct memory实现ByteBuffer池的方案(如下简称direct buffer),经过JNI在内存开辟一段空间来使用,该方案在早期的MINA版本中是一个很是好的特性,那是由于MINA开发初期,JVM并无如今的强大,带有池效果的direct buffer性能比较好。可是当咱们使用-Xms -Xmx等指令增长JVM可以使用的内存,那仅仅增长了堆的内存空间,而direct memory的空间并无增长,致使MINA实际使用的时候常常出现OutOfMemoryError。若是你的确想使用direct memory,能够经过-XX:MaxDirectMemorySize选项来设置。不过笔者不建议这样作,由于最新的测试代表,在现代的JVM里面,direct memory比堆的表现更差。这里可能有读者会以为奇怪,为何不用池,而要用堆呢,并且还须要gc。那是由于如今的JVM gc能力已经很强了,并且在并发环境里面,pool的同步也是一个性能的问题。咱们能够经过这样的代码进行设置 MINA 2.0已经默认把直接内存分配改为堆,为了提供最好的性能和稳定性。

ByteBuffer.setUseDirectBuffers(false);   
ByteBuffer.setAllocator(new SimpleByteBufferAllocator());

最后一条优化技巧就是,把你的应用部署在Linux上,而且打开Java NIO使用Linux epoll的功能。可能你还没听过epoll,可是你应该听过Lighttpd、Nginx、Squid等,得益于epoll,它们提供很高的网络性能,还占用很是少的系统资源。JDK6已经默认把epoll配置打开,所以笔者建议把你的应用部署在JDK6上面,也同时由于JDK6还有别的优化特性。若是你的应用必须部署在JDK5上,你也能够经过参数把epoll支持打开。


最后附上项目地址:http://git.oschina.net/bob4j/spring_mina/tree/master

相关文章
相关标签/搜索