最近有个项目,需要有大量的Tomcat并发,之前对这方面了解不多,因此进行了深入研究,总结一下心得。希望对大家也有些帮助!
首先,遇到第一个问题:
三月 07, 2019 6:37:46 下午 org.apache.tomcat.util.net.NioEndpoint$Acceptor run
严重: Socket accept failed
java.io.IOException: 打开的文件过多
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:1316)
at java.lang.Thread.run(Thread.java:745)
这个问题,网上有很多解答,主要都是关于修改Linux底层文件句柄的最大上限的,但是实际上,要根据自己的项目分析为什么文件打开过多。当时针对这问题,做了如下三个改动:
1、修改Linux底层文件句柄
【原因】:系统(Centos 7.3)默认最大打开文件句柄为1024,我需要并发远大于这个数量级,因此调高次参数
【方法】:
/etc/security/limits.conf文件End file 前增加以下两行,重启系统
* soft nofile 65536
* hard nofile 65536
确认修改是否成功 ulimit -a
2、排查代码中未关闭的文件流,网络IO流
【原因】:每个打开文件流、网络IO流,如socket之类的,都需要消耗文件句柄,如果不关闭,则会造成泄漏
【方法】:
finally里面关闭所有socket、文件流,不详细展开
监控文件句柄数:
lsof -p 29023 查看线程29023打开的文件句柄详细情况
lsof -p 29023 | wc -l 统计线程29023打开句柄数量
3、快速释放tomcat闲置连接
【原因】:怀疑Tomcat连接一直未释放连接,是否有效未独立验证
【方法】:修改tomcat连接超时时间,connectionTimeout="10000" 10s(依据项目具体情况而定)
华丽分隔线
第二个问题:Mysql 出现问题:
Caused by: java.sql.SQLException: Too many active concurrent transactions
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3976)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3912)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2486)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858)
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2079)
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1756)
这个问题,还真是Mysql的bug,吓我一跳,说是mysql 5.1 同时启动的事务最多1023个,mysql 5.5才修复这个问题。一想到要把整个系统的mysql版本都更换了,内心一顿烦躁。
https://bugs.mysql.com/bug.php?id=26590
晚上回去仔细思考了一下,虽然是并发但是我并没有启动事务,仅仅是用hibernate update方法啊,为啥会引发这个错误呢?
后来,把1个多g的日志重新拿出来check一下,果然在大量的这个错误前,还隐藏了一个少量的错误,请继续往下看
华丽分隔线
第三个问题:c3p0连接池取不到数据库连接
Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@63577d06 -- timeout at awaitAvailable()
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1317)
at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:557)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:477)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:525)
... 45 more
【原因】:我们后台是servlet去接收并发消息的,servlet是单例多线程的,也就是大量并发过来时,是多个线程同时处理,数据库也是同时操作,监控数据库的连接数,show status like "%Thread%";当并发一产生时,立刻达到c3p0的连接数,<property name="maxPoolSize" value="650"/>
【方法】:
<property name="maxIdleTime" value="30"/> 大大减少获取不到连接的概率(只有重启网关的时候才会有错误,过30+s后连接数正常恢复,长时间未测试过)
<prop key="hibernate.connection.release_mode">after_transaction</prop> 该修改项对性能有优化,能减少获取不到连接的概率,但影响功能,代码回退,建议大家根据自己的系统确认下是否按照这个配置优化
华丽分隔线
前三步之后,出现连接取不到的概率大大减少,但偶尔也会产生
【方法】:代码修改,生产者消费者方式,后台处理servlet时候,启动队列,后台定时消费队列。
改完之后,没有再出过错误,数据库连接也不会疯涨了,Close