这个问题,本来应该在文档中进行提示和重点说明的内容,但是框架本身并没有提示,NIOsocketconection和session需要手动进行关闭的问题。这个问题并不是高并发才会产生的,只要持续一段就会发生这个问题,默认1024的文件描述符很快就会保罗问题。
参考 http://blog.csdn.net/w93223010/article/details/9027605
https://www.cnblogs.com/jacklondon/archive/2011/03/16/1985926.html
一、问题描述
产生 java.io.IOException: Too many open files 错误。
通过 lsof -c java 观察程序描述符占用情况会出现成堆的
java pid -- 20r FIFO 0,6 pipe
20w FIFO
这一类占用
1.打开的文件没有关闭
2.通讯连接没有完全关闭
二、他人解决办法
a. 使用到 NioSocketAcceptor 一个,用来 listen ,没有问题。
b. 自定义 IoHandlerAdapter 在 Mina 中是 Singleton, 只创建一次,也没有问题。
c. 自定义 IoHandlerAdapter 中需要有以下代码:
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
session.close(true);//force close right now
}
public void sessionOpened(IoSession session) throws Exception {
session.getConfig().setBothIdleTime(180);//set timeout seconds, must
}
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
session.close(true);//timeout now, close it
}
f. 系统中使用了以下 Mina NIO 的 Java 对象:
NioSocketAcceptor 用来 listen, 1个,配 1 个 IoHandlerAdapter
每次 1 客户 socket 连接,对应1 个 IoSession, 创建 1 个 NioSocketConnector 连接后端服务器,然后自动创建一个 IoSession 作为当前客户 socket 的 peer (同伴),也就是两个 IoSession 有对应关系。
g. 无论是客户 socket 连接的 IoSession 还是 peer IoSession , 在 sessionClosed 中需要调用 peer.close(false); 这里的 close(false) 不是立即关闭,而是让 peer 发完数据再关闭。这是由 NIO 这种异步操作特性决定的。这里不会造成死循环: client IoSession 调用 peer.close(false), 而 peer 反过来调用 client IoSession.close(false)。好像 Mina 做了特别处理。
h. 特别的提醒,NioSocketConnector 也要关闭。函数名是 dispose()。这点特别重要。这次出现 too many open files 的问题根源在这里。而 Mina 文档中只字不提。而 NioSocketConnector 与 peer IoSession 使用 127.0.0.1 端随机端口连接,匪夷所思。
而 peer IoSession 关闭后,没有关闭 NioSocketConnector , 也没有给它发 close event, 或者让它进入 exception, 这种设计也不好。 IoSession 关闭后,留着 NioSocketConnector 也是无用,还白白成了一个 ESTABLISHED 状态的连接,导致 socket leak。 这似乎就是所谓的半开 socket ? 还是觉得 Mina 这种设计不好。
三、我的解决方式
针对第c 和第h条
讲NioSocketConnector 实例存储到session
NioSocketConnector conn =null;
IoSession session = null;
ConnectFuture connectFuture = null;
try{
conn = new NioSocketConnector();
DefaultIoFilterChainBuilder chain = conn.getFilterChain();
chain.addLast("logger", new LoggingFilter());
chain.addLast("mdc", new MdcInjectionFilter());
chain.addLast("codec", new ProtocolCodecFilter(protocolCodecFactory));
conn.setHandler(ioHandler);
//重复三次
conn.setConnectTimeoutMillis(DefaultConnectTimeOut);
connectFuture = conn.connect(addr);
connectFuture.awaitUninterruptibly();
if (connectFuture.isDone()) {
if (!connectFuture.isConnected()) { //若在指定时间内没连接成功,则抛出异常
LOGGER.info("fail to connect " );
conn.dispose(); //不关闭的话会运行一段时间后抛出,too many open files异常,导致无法连接
throw new Exception();
}
}
session = connectFuture.getSession();
session.setAttribute(CONNECTER,conn);
LOGGER.debug("链接成功{},{}",session.getId(),session.getRemoteAddress());
然后在后续的
exceptionCaught
sessionOpened
sessionIdle
中取出connection 判断装填然后dispose
NioSocketConnector conn =(NioSocketConnector) session.getAttribute(SocketUtils.CONNECTER);
if( conn != null && conn.isActive()){
conn.dispose();
LOGGER.debug("connection is closed");
}
session.closeNow();
四、目前文件描述符数目可控,看看运行结果