当启动失败时,日志里有如下的异常,看起来似乎和网络有关。
java.sql.SQLRecoverableException: I/O Exception: Connection reset at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:281) at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:118) at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:224) at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:296) at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:611) at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:455) at oracle.jdbc.driver.PhysicalConnection.<init>(PhysicalConnection.java:494) at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:199) at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:30) at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:503) at java.sql.DriverManager.getConnection(DriverManager.java:582) at java.sql.DriverManager.getConnection(DriverManager.java:154) |
根据网上的资料,在sqlnet.ora文件中定义SQLNET.INBOUND_CONNECT_TIMEOUT变量,经过尝试,设置为0或者一个比较大的值如30,都可以消除掉前述问题。根据资料介绍,这个变量用来控制客户端通过认证的时间间隔,假如认证时间超时,则本次数据库链接创建操作就会失败,而缩短超时时间,可以有效的阻止DoS类型的攻击。根据安全加固操作指导,加固操作确实包含用于修改认证超时时间的指令。
问题定位到这里,应该说找到了规避手段,也了解引发问题的初因,但现场实施人员对于我的解释并不满意。好在我又找到一条新线索。
新线索
从Oracle官网论坛里找到一个帖子,讨论的问题和我遇到的问题类似,但提出的问题原因和解决方法比较有意思。按照帖子里的说法,问题的根因和Java的安全随机数生成器的实现原理相关。
java.security.SecureRandom is a standard API provided by sun. Among various methods offered by this class void nextBytes(byte[]) is one. This method is used for generating random bytes. Oracle 11g JDBC drivers use this API to generate random number during login. Users using Linux have been encountering SQLException("Io exception: Connection reset").
The problem is two fold
1. The JVM tries to list all the files in the /tmp (or alternate tmp directory set by -Djava.io.tmpdir) when SecureRandom.nextBytes(byte[]) is invoked. If the number of files is large the method takes a long time to respond and hence cause the server to timeout
2. The method void nextBytes(byte[]) uses /dev/random on Linux and on some machines which lack the random number generating hardware the operation slows down to the extent of bringing the whole login process to a halt. Ultimately the the user encounters SQLException("Io exception: Connection reset")
Users upgrading to 11g can encounter this issue if the underlying OS is Linux which is running on a faulty hardware.
Cause The cause of this has not yet been determined exactly. It could either be a problem in your hardware or the fact that for some reason the software cannot read from /dev/random
Solution Change the setup for your application, so you add the next parameter to the java command:
-Djava.security.egd=file:/dev/../dev/urandom |
现场实施人员对于这个帖子里的信息比较感兴趣。按照帖子里的修改方法,在测试环境和生产环境做了多次验证,惊喜的发现问题得到了解决。
随机数生成器
如果不是为了解决问题,平时也不会去刻意查阅底层实现相关的原理,这次是个好机会。网上关于/dev/random的介绍很多,只列出要点:
1) /dev/random是Linux内核提供的安全随机数生成设备;
2) /dev/random依赖系统中断信息来生成随机数,因而设备数目比较少时,产生随机数的速度比较慢,当应用对随机数的需求比较大时会供不应求;
3) /dev/random在读取时会阻塞调用线程;
4) /dev/urandom是/dev/random的改良版本,解决了随机数生成慢、阻塞调用的问题,但同时稍微降低了安全性;
5) Linux环境下man random命令可以查阅到/dev/random和/dev/urandom的介绍,比较详尽;
附:
回到tomcat文档里的建议,采用非阻塞的熵源(entropy source),通过java系统属性来设置:
-Djava.security.egd=file:/dev/./urandom
这个系统属性egd表示熵收集守护进程(entropy gathering daemon),但这里值为何要在dev
和random
之间加一个点呢?是因为一个jdk的bug,在这个bug的连接里有人反馈及时对 securerandom.source 设置为 /dev/urandom
它也仍然使用的 /dev/random
,有人提供了变通的解决方法,其中一个变通的做法是对securerandom.source设置为 /dev/./urandom
才行。也有人评论说这个不是bug,是有意为之。
在JAVA中的配置发生器
在JAVA中可以通过两种方式去设置指定的随机数发生器
1. -Djava.security.egd=file:/dev/random或者 -Djava.security.egd=file:/dev/urandom
2. 修改配置文件java.security 在jvm_home\jre\lib\security
参数securerandom.source=file:/dev/urandom
/dev/random 是堵塞的,在读取随机数的时候,当熵池值为空的时候会堵塞影响性能,尤其是系统大并发的生成随机数的时候,如果在随机数要求不高的情况下,可以去读取/dev/urandom
整个流程如下:
JAVA中首先读取系统参数java.security.egd,如果值为空的时候,读取java.security配置文件中的参数securerandom.source, 在通常情况下,就是读取参数securerandom.source,默认值是/dev/urandom,也就是因该是不堵塞的。
但实际情况是,在测试linux环境下的时候,你会发现默认值却是堵塞的。
查看代码sun.security.provider.SeedGenerator.java
在代码中可以看到当配置值是file:/dev/random或者file:/dev/urandom的时候,启用NativeSeedGenerator, 而在linux下的NativeSeedGenerator类
只是简单的继承了一下URLSeedGenerator, 而对URLSeedGenerator默认的构造函数里则强制读取了文件/dev/random
也就是说哪怕设置了-Djava.security.egd=file:/dev/urandom,最后的结果一样是读取file:/dev/random, 不清楚究竟是代码的bug,还是有意为之?但解决办法相当的有趣,因为在上面的代码中强匹配了file:/dev/random,file:/dev/urandom的值
而对linux来说要表示urandom的路径就很多种方式了
比如file:/dev/./urandom 或者 file:/dev/../dev/urandom 这样就可以绕过JAVA的简单检查了,这应该是JAVA的一个security的bug
设置
就可以绕过保护,而使用URLSeedGenerator,读取文件/dev/./urandom 也就是/dev/urandom文件
Tip: 打开security的debug log
通过设置参数
-Djava.security.debug=all
可以控制台看到所有security的log
参考资料:
http://blog.csdn.net/jackie_xiaonan/article/details/37740359
http://ifeve.com/jvm-random-and-entropy-source/
http://blog.csdn.net/raintungli/article/details/42876073