全新的CentOS,全新的Tomcat。
啓動Tomcat後,關閉Tomcat報錯:
[root@aliyun bin]# ./shutdown.sh
Using CATALINA_BASE: /home/tomcat
Using CATALINA_HOME: /home/tomcat
Using CATALINA_TMPDIR: /home/tomcat/temp
Using JRE_HOME: /usr
Using CLASSPATH: /home/tomcat/bin/bootstrap.jar:/home/tomcat/bin/tomcat-juli.jar
Sep 04, 2018 5:16:30 PM org.apache.catalina.startup.Catalina stopServer
SEVERE: Could not contact localhost:8005. Tomcat may not be running.
Sep 04, 2018 5:16:30 PM org.apache.catalina.startup.Catalina stopServer
SEVERE: Catalina.stop:
java.net.ConnectException: Connection refused (Connection refused)
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at java.net.Socket.connect(Socket.java:538)
at java.net.Socket.<init>(Socket.java:434)
at java.net.Socket.<init>(Socket.java:211)
at org.apache.catalina.startup.Catalina.stopServer(Catalina.java:450)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.catalina.startup.Bootstrap.stopServer(Bootstrap.java:400)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:487)
報錯顯示shutdown端口8005都沒有,tomcat沒啓動起來。查看日誌,並沒有報錯。
tomcat真的沒有啓動起來沒,事實上也確實如此,等上幾分鐘後再執行shutdown.sh,可以正常關閉tomcat了。
查看tomcat日誌,發現SecureRandom的創建操作花費了大量時間,幾百秒,所以tomcat啓動很慢,而沒等到tomcat啓動起來就執行shutdown,就會發生前面的錯誤。
[root@aliyun logs]# cat catalina.out |grep Random
04-Sep-2018 16:49:02.202 INFO [localhost-startStop-1] org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [238,145] milliseconds.
以下是網上找到的資料:
根本原因是 SecureRandom 這個 jre 的工具類的問題。那爲什麼 SecureRandom generateSeed 這麼慢,甚至掛在 Linux 操作系統呢?
Tomcat 7/8 都使用 org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom 類產生安全隨機類 SecureRandom 的實例作爲會話 ID。
Tomcat 使用 SHA1PRNG 算法是基於 SHA-1 算法實現且保密性較強的僞隨機數生成器。
在 SHA1PRNG 中,有一個種子產生器,它根據配置執行各種操作。
Linux 中的隨機數可以從兩個特殊的文件中產生,一個是 /dev/urandom,另外一個是 /dev/random。他們產生隨機數的原理是利用當前系統的熵池來計算出固定一定數量的隨機比特,然後將這些比特作爲字節流返回。熵池就是當前系統的環境噪音,熵指的是一個系統的混亂程度,系統噪音可以通過很多參數來評估,如內存的使用,文件的使用量,不同類型的進程數量等等。如果當前環境噪音變化的不是很劇烈或者當前環境噪音很小,比如剛開機的時候,而當前需要大量的隨機比特,這時產生的隨機數的隨機效果就不是很好了。
這就是爲什麼會有 /dev/urandom 和 /dev/random 這兩種不同的文件,後者在不能產生新的隨機數時會阻塞程序,而前者不會(ublock),當然產生的隨機數效果就不太好了,這對加密解密這樣的應用來說就不是一種很好的選擇。/dev/random 會阻塞當前的程序,直到根據熵池產生新的隨機字節之後才返回,所以使用 /dev/random 比使用 /dev/urandom 產生大量隨機數的速度要慢。
SecureRandom generateSeed 使用 /dev/random 生成種子。但是 /dev/random 是一個阻塞數字生成器,如果它沒有足夠的隨機數據提供,它就一直等,這迫使 JVM 等待。鍵盤和鼠標輸入以及磁盤活動可以產生所需的隨機性或熵。但在一個服務器缺乏這樣的活動,可能會出現問題。
有2種解決方案:
1. 在Tomcat環境中解決:
可以通過配置 JRE 使用非阻塞的 Entropy Source:
在 catalina.sh 中加入這麼一行:-Djava.security.egd=file:/dev/./urandom 即可。
2. 在 JVM 環境中解決:
打開 $JAVA_PATH/jre/lib/security/java.security 這個文件,找到下面的內容:
1 |
|
替換成:
1 |
|
這裏值爲何要在 dev 和 random 之間加一個點呢?是因爲一個 JDK 的 bug,有人反饋即使對 securerandom.source 設置爲 /dev/urandom 它也仍然使用的 /dev/random,有人提供了變通的解決方法,其中一個變通的做法是對 securerandom.source 設置爲 /dev/./urandom 才行。也有人評論說這個不是 bug,是有意爲之。
在 JDK 7 的 java.security 文件裏,配置裏的是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
但這個 /dev/urandom 也同那個 bug 報告裏所說的等同於 /dev/random;要使用非阻塞的熵池,這裏還是要修改爲 /dev/./urandom。經測試,貌似 JDK 7 並沒有同註釋裏的意思修復了這個問題。