1. 程式人生 > >CentOS7 Tomcat 啟動過程很慢,JVM上的隨機數與熵池策略

CentOS7 Tomcat 啟動過程很慢,JVM上的隨機數與熵池策略

1. CentOS7 Tomcat 啟動過程很慢

在centos啟動官方的tomcat時,啟動過程很慢,需要幾分鐘,經過檢視日誌,發現耗時在這裡:是session引起的隨機數問題導致的:

14-Jul-2016 04:14:22.900 INFO [localhost-startStop-1] org.apache.catalina.util.SessionIdGenerator.createSecureRandom Creation of SecureRa
ndom instance for session ID generation using [SHA1PRNG] took [142,673] milliseconds.

2. JVM上的隨機數與熵池策略

在apache-tomcat官方文件:如何讓tomcat啟動更快裡面提到了一些啟動時的優化項,其中一項是關於隨機數生成時,採用的“熵源”(entropy source)的策略。

他提到tomcat7的session id的生成主要通過java.security.SecureRandom生成隨機數來實現,隨機數演算法使用的是”SHA1PRNG”

private String secureRandomAlgorithm = "SHA1PRNG";

在sun/oracle的jdk裡,這個演算法的提供者在底層依賴到作業系統提供的隨機資料,在linux上,與之相關的是/dev/random和/dev/urandom,對於這兩個裝置塊的描述以前也見過討論隨機數的文章,wiki中有比較詳細的描述,摘抄過來,先看/dev/random :

在讀取時,/dev/random裝置會返回小於熵池噪聲總數的隨機位元組。/dev/random可生成高隨機性的公鑰或一次性密碼本。若熵池空了,對/dev/random的讀操作將會被阻塞,直到收集到了足夠的環境噪聲為止

而 /dev/urandom 則是一個非阻塞的發生器:

dev/random的一個副本是/dev/urandom (”unlocked”,非阻塞的隨機數發生器),它會重複使用熵池中的資料以產生偽隨機資料。這表示對/dev/urandom的讀取操作不會產生阻塞,但其輸出的熵可能小於/dev/random的。它可以作為生成較低強度密碼的偽隨機數生成器,不建議用於生成高強度長期密碼。  另外wiki裡也提到了為什麼linux核心裡的隨機數生成器採用SHA1雜湊演算法而非加密演算法,是為了避開法律風險(密碼出口限制)。

回到tomcat文件裡的建議,採用非阻塞的熵源(entropy source),通過java系統屬性來設定:

-Djava.security.egd=file:/dev/./urandom

catalina.sh

if [[ "$JAVA_OPTS" != *-Djava.security.egd=* ]]; then
    JAVA_OPTS="$JAVA_OPTS -Djava.security.egd=file:/dev/./urandom"
fi

這個系統屬性egd表示熵收集守護程序(entropy gathering daemon),但這裡值為何要在dev和random之間加一個點呢?是因為一個jdk的bug,在這個bug的連線裡有人反饋及時對 securerandom.source 設定為 /dev/urandom 它也仍然使用的 /dev/random,有人提供了變通的解決方法,其中一個變通的做法是對securerandom.source設定為 /dev/./urandom 才行。也有人評論說這個不是bug,是有意為之。

我看了一下我當前所用的jdk7的java.security檔案裡,配置裡仍使用的是/dev/urandom:

# Select the source of seed data for SecureRandom. By default an  # attempt is made to use the entropy gathering device specified by  # the securerandom.source property. If an exception occurs when  # accessing the URL then the traditional system/thread activity  # algorithm is used.  #  # On Solaris and Linux systems, if file:/dev/urandom is specified and it  # exists, a special SecureRandom implementation is activated by default.  # This “NativePRNG” reads random bytes directly from /dev/urandom.  #  # On Windows systems, the URLs file:/dev/random and file:/dev/urandom  # enables use of the Microsoft CryptoAPI seed functionality.  #  securerandom.source=file:/dev/urandom

我不確定jdk7裡,這個 /dev/urandom 也同那個bug報告裡所說的等同於 /dev/random;要使用非阻塞的熵池,這裡還是要修改為/dev/./urandom 呢,還是jdk7已經修復了這個問題,就是同註釋裡的意思,只好驗證一下。

使用bug報告裡給出的程式碼:

import java.security.SecureRandom;
class JRand {
    public static void main(String args[]) throws Exception {
        System.out.println("Ok: " +
            SecureRandom.getInstance("SHA1PRNG").nextLong());
    }
}

然後設定不同的系統屬性來驗證,先是在我的mac上:

% time java -Djava.security.egd=file:/dev/urandom  JRand
Ok: 8609191756834777000
java -Djava.security.egd=file:/dev/urandom JRand  
0.11s user 
0.03s system 
115% cpu 
0.117 total

% time java -Djava.security.egd=file:/dev/./urandom  JRand
Ok: -3573266464480299009
java -Djava.security.egd=file:/dev/./urandom JRand  
0.11s user 
0.03s system 
116% cpu 
0.116 total

可以看到/dev/urandom和 /dev/./urandom 的執行時間差不多,有點納悶,再仔細看一下wiki裡說的:

FreeBSD作業系統實現了256位的Yarrow演算法變體,以提供偽隨機數流。與Linux的/dev/random不同,FreeBSD的/dev/random不會產生阻塞,與Linux的/dev/urandom相似,提供了密碼學安全的偽隨機數發生器,而不是基於熵池。而FreeBSD的/dev/urandom則只是簡單的連結到了/dev/random。  儘管在我的mac上/dev/urandom並不是/dev/random的連結,但mac與bsd核心應該是相近的,/dev/random也是非阻塞的,/dev/urandom是用來相容linux系統的,這兩個隨機數生成器的行為是一致的。參考這裡

然後再到一臺ubuntu系統上測試:

% time java -Djava.security.egd=file:/dev/urandom JRand  Ok: 6677107889555365492  java -Djava.security.egd=file:/dev/urandom JRand  0.14s user 0.02s system 9% cpu 1.661 total

% time java -Djava.security.egd=file:/dev/./urandom JRand  Ok: 5008413661952823775  java -Djava.security.egd=file:/dev/./urandom JRand  0.12s user 0.02s system 99% cpu 0.145 total  這回差異就完全體現出來了,阻塞模式的熵池耗時用了1.6秒,而非阻塞模式則只用了0.14秒,差了一個數量級,當然代價是轉換為對cpu的開銷了。

// 補充,連續在ubuntu上測試幾次/dev/random方式之後,導致熵池被用空,被阻塞了60秒左右。應用伺服器端要避免這種方式。