为了实现高性能的MOM(消息中间件),学习了java的NIO,想利用NIO技术来改善网络通讯的性能。学习NIO的时候,看了书上的,也找了网上相当多的资源,但大部分的资料都是比较粗浅的,入门级,深入的基本没有。不过,对于了解和学习NIO来说已经足够了。真正的领会还是需要在实践中获得的。
NIO的技术在JDK1.4中出现。NIO最大的特点有两个:一个是块状读写;另外一个是多路复用技术(multiplex)。第一个特点改变了老式IO中,每次读写只有一个字节(stream)和两个字节(reader)的方式,提高了读写的速度;第二个特点改变了原来读写socket时候阻塞的同步状况,使socket的读写具有的异步的功能。第二个特点比较重要,NIO的异步读写据说大大提高了网络通讯的能力,尽管没有C++的快,但至少不再象以前那样,一说起java首先想到的是“性能低下”。这一点,在网上可以搜到一大把的资料。
在JTangMQ消息中间件中,使用了NIO实现了通讯层。为了方便起见,客户端和服务器都采用了同一块代码,也就是说,客户端和服务器端都是非阻塞的,编解码和读写socket的方式都一样。然而,在测试过程中发现,客户端在向服务器发送消息的时候,cpu的占用率在80%左右,带宽只占了5%(100M的带宽);服务器端的配置比较高,cpu的占用率在10%左右,带宽占有率一样。通过java的profiler工具分析,发现客户端的cpu时间主要是消耗在选择器的select操作之上(NIO的多路复用),而select操作主要调用的是poll函数,这个函数是sun公司提供的关于选择器的缺省类。根据网上的资料,这个操作是一个轮询socket的同步操作,它的主要功能就是不断地去查询向该选择器注册的socket,看看有没有可做的操作,如有,则返回,没有,继续不断地轮询。也就说,分配的cpu的时间基本上被消耗在poll上了。
现在问题就出来了。既然poll占用这么多时间,是因为没有什么东西可以写入socket或者从socket中读取;那么,就应该给“提供读写数据的线程”更多的cpu时间,使得每一次的poll都有可作的操作,使时间耗费在读写socket之上,而不是poll之上。然而,从分析数据中看来,poll线程显然比“提供读写数据的线程(wr线程)”占用了更多的cpu时间,假设他们的优先级相同,也假设操作系统是公平的分配cpu时间,那么它们得到的cpu时间应该是一样的,此时,造成它们占用cpu时间不同的原因就很可能是这样:在相同的时间内,wr线程提供的数据量根本不够poll线程处理,poll线程处理在相同的时间内处理这些数据绰绰有余,那么,poll线程更多的时间就耗费在轮询上了。到这里,我们假设的是这两个线程分配到了相同的时间,所以,即使poll线程大部分时间是在做poll轮询操作,那么所占用的时间最大值也是有限的,不会超过线程分配得到的时间。但是,如果此时尽管wr线程得到了cpu时间,但是它在处理过程中主动放弃cpu(比如执行wait操作),那么,在cpu时间总量相等的情况下,由于poll线程没有执行wait操作,因此,就会得到更多的cpu时间,这样,就会出现上面的分析的情况,大部分的cpu时间都给poll的轮询操作了。
由此分析,如果使wr线程不断的运行,而使poll线程在没有数据的时候放弃cpu时间,就应该可以减少poll线程的轮询操作,而使wr线程得到更多的cpu时间,提供更多的可供读写的数据,提高读写的效率。然而,由于poll轮询操作是一个不间断的过程,是sun公司提供的底层的api,是无法修改的。所以,就要放弃poll操作,也就是要放弃选择器。换句话说,就是客户端最好是不要采用非阻塞的方式。
综上所分析,如果客户端采用非阻塞的方式, 应该是可以提高客户端读写socket的速度的。而服务器端由于要同时连接大量的客户端,还是继续采用非阻塞方式为好。
这只是分析的结果,还没有具体实验过。不知道有没有人遇到过类似的问题,请不吝赐教。