项目中引入NIO纯属万不得已,因为这个问题在除NIO外似乎没有太好的解决方式了。
O(n)的问题
我们的网关,在服务器看来其实就是一个一个客户端,服务器根本不知道网关的存在。而客户端与服务器连接的模型,是TCP的长连接,即客户端连接上服务器以 后,这个连接在客户端断线以前都是存在的。因此,网关也必须帮客户端保持这个连接,结果是,如果使用传统阻塞IO的方式的话,必须有至少新建一个线程用于 维护读的操作。那么,试想一下,如果有1000个用户登录上来的话,就必须新建1000个线程用于守护,这样的结果是不能忍受的。而且还会引出更大的问 题。
OutOfMemory
建立在上面的传统IO基础上,我们进行了一些测试,结果是不能令人满意的,在1000多用户的时候,出现了内存溢出:java.lang.OutOfMemoryError: unable to create new native thread.这里需要说一下,内存溢出不单只是在JVM耗尽了分配给他的内存时出现,换个角度想,线程是属于系统级的东西,所以,如果操作系统没有足够的内存来产生这个线程,也同样会造成内存溢出。上面这个内存溢出就属于系统的内存溢出。
之前查资料的情况是,Windows和Linux一个进程内的线程数是有限制的,具体跟系统不用有所不同。我们在Windows下测试的情况是在5000~7000左右。同时,这么大的线程数,应用单调度的效率都会大大降低。这里还想说一个JVM的配置,-Xss。这个参数是配置JVM分配给一个线程的栈大小的,JDK5.0时,一个线程默认大小应该为1M,所以根据应用情况,把这个值改小的话,同样的内存将能生成更多的线程,同时减少JVM内存溢出的可能性。
O(1)的解决
上面的问题中,最大的问题就是需要一个进程维护被阻塞的“读”操作(PS:写操作是没有关系),因此只要能把“读”部分的阻赛线程化解掉即可。NIO用在这里,再合适不过了。通过NIO,这里的连接模型发生变化了。以前是:N个用户-N个线程-N个IO-N个处理逻辑(数据读取过来以后需要进行业务处理) 现在是 N个用户-k个线程(1个或多个NIO的selector)-N个IO-1个处理线程池。后者的连接部分、处理部分都要用户多少无关。线程池配置多大、使用多少个selector都可根据实际测试情况进行配置了,大大减少了线程数量。
NIO的困惑
- NIO是个好东东,不过要很好的驾驭还是一件很困难的事情。NIO最麻烦的地方就在传输消息的不完整性(个 人的说法),主要就是在消息量大的情况下NIO一次传输的消息可能是多次发送积累的结果,所以内容可能是多条消息,可能是某条消息的中间部分。我们的应用 因为一个用户一个Socket连接,所以消息量比较小,目前还没有发生这种情况,但是这里一直是一个隐患。解决方式也没有太好的办法,只能通过缓冲收到的消息,然后再通过关键字符或者内容长度的标准(如:HTTP中的Cotent-Length)进行内容划分。
- 线 程数是上去了,但是连接数还受到限制。这还是一个操作系统的问题,Windows系统(XP)对TCP连接,默认情况下只能使用5000以下的端口。如果 要使用其他端口,需要修改一下注册表。其他操作系统不是很清楚,不过从查到的资料来看应该都有一些限制。Windows XP系统修改方法如下:查 找注册表项HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/ Parameters,在其中加入新值 MaxUserPort 类型为DWORD,此键标识最大允许的端口号,自己写一个就行,小于65535,重启电脑。
- 连接未释放的问题。这个可以参考以前我写过的一篇:网络连接无法释放—— CLOSE_WAIT
- NIO方式常用于服务器端,因为服务器总会面临这种大压力的情况。但是对于我们网关,是客户端去连服务器的,NIO方式也同样适用于客户端的程序。连接方式与服务器端相同,SocketChannel连接上以后注册到相应的Selector即可。
- 两个小问题,费了我不少时间。ByteBuffer这个东西,不管读还是写,完了记着调用一下flip()方法,不然不是没读出来就是没写进去。只是这个方法可读性也太次了,老忘,ft。还有一个,使用NIO方式进行网络连接的时候(客户端连接服务器),除了调用channel的connect外,等连接上了(触发了Connect事件),记着还要调用一下SocketChannel的finishConnect()方法,以完成整个连接过程,否则会出现一些不知名的问题。。。
站在巨人肩上
虽然自己写的东西目前用着还可以,但是说实话,写的太简陋了,所以指不定哪天就出现一些不知名的问题了,呵呵。所以下来又找了一些支持NIO的框架。
--Grizzly :一个开源的NIO框架。GlassFish的HTTP服务器底层实现。同时Grizzly已经实现好了一个基于NIO的HTTP底层,所以如果是 HTTP协议的话,拿来就可以用了。个人还是比较看好这个的,毕竟Sun在支持。由于Grizzly的初衷是搭建一个基于NIO支持不用协议的框架,所以 在NIO之上它还提供了一些接口,用户自己实现这些接口,即可分割不同协议。HTTP只是其中的一种协议实现而已。
--MINA :Apache的项目。MINA旨在提供一个高性能的网络传输框架。所以它对NIO的支持,主要在Socket一层进行封装,使得用户使用起了会比较方 便。个人用了一下,确实很简单。不过,MINA也就到此为止了,再往上就需要用户自己实现消息的分割,协议解析一些东西了。
--QuickServer :这个东东主要是提供服务器端的支持,使用起来也是相当简单的。只是我们的应用是客户端的,所以用不上,写在这里,大家有兴趣可以研究一下。