2016-05-14 89 views
1

[總結&回答:顯然問題是播種隨機數發生器需要很長時間。請參閱下面的答案。 ]爲什麼第一次使用scrypt()只需要1%的CPU,而在GCE中需要半個小時?

在Google Compute Engine(GCE)中,我的Java虛擬機應用程序對scrypt密碼哈希函數的首次請求花費很長時間 - 因爲代碼尚未進行即時編譯。所以我通過在服務器啓動時調用 虛擬scrypt("pswd", 2,1,1)調用來使scrypt變暖。然而,會發生什麼呢,是CPU上升到300%+,在那裏停留10-20秒,然後回落到1%,儘管對scrypt()的請求尚未完成。現在,CPU停留在1%,持續很多分鐘(長達半小時,2 GCE vCPU),直到最終完成scrypt()。

爲什麼這種奇怪的行爲?

爲什麼scrypt()會以300%的CPU持續運行,直到完成爲止?這不是 內存不足。看下面的Docker統計數據。

第1次scrypt()請求後,後續請求「立即」結束。例如,這樣的: SCryptUtil.scrypt("pswd", 65536, 8, 1) 需要< 0.2秒,雖然它做更多的工作比: SCryptUtil.scrypt("pswd", 2, 1, 1) 這(如前所述)是我的第一個scrypt()調用,通常只需要幾分鐘的時間,與4 GCE個vCPU - 並且通常在大約半個小時內,使用2個GCE vCPU。

我正在使用4個vCPU,3.6 GB RAM的GCE實例。 Docker 1.11.1。 OpenJDK 1.8.0_77。在Alpine Linux 3.3 Docker容器中,Ubuntu 16.04 Docker主機。無法在筆記本電腦上重現此問題;在我的筆記本電腦上,scrypt總是很快,不需要任何熱身。

docker stats,5-10秒後:(現在edp_play_1,線路2,使用300 +%CPU)

CONTAINER   CPU %    MEM USAGE/LIMIT  MEM %    NET I/O    BLOCK I/O   PIDS 
edp_nginx_1   0.02%    55.92 MB/104.9 MB 53.33%    6.191 kB/2.897 kB 0 B/0 B   6 
edp_play_1   315.12%    914.7 MB/2.831 GB 32.31%    43.4 kB/66.09 kB 0 B/2.58 MB  67 
edp_postgres_1  0.33%    29.84 MB/314.6 MB 9.49%    529.1 kB/307.9 kB 0 B/327.7 kB  17 
edp_redis_1   0.08%    6.513 MB/52.43 MB 12.42%    4.984 kB/1.289 kB 0 B/0 B   3 

docker stats半分鐘後:(現在edp_play_1僅使用0.97%的CPU - 並保持等這一點,長達一個半小時,直到完成)

CONTAINER   CPU %    MEM USAGE/LIMIT  MEM %    NET I/O    BLOCK I/O   PIDS 
edp_nginx_1   0.02%    55.92 MB/104.9 MB 53.33%    6.341 kB/3.047 kB 0 B/0 B   6 
edp_play_1   0.97%    1.011 GB/2.831 GB 35.71%    130.2 kB/215.2 kB 0 B/5.546 MB  66 
edp_postgres_1  0.28%    29.84 MB/314.6 MB 9.49%    678.2 kB/394.7 kB 0 B/458.8 kB  17 
edp_redis_1   0.06%    6.513 MB/52.43 MB 12.42%    4.984 kB/1.289 kB 0 B/0 B   3 

如果你想在斯卡拉& SBT測試,這是我在GCE會發生什麼:

scala> import com.lambdaworks.crypto.SCryptUtil 
import com.lambdaworks.crypto.SCryptUtil 

scala> def time[R](block: => R): R = { val t0 = System.nanoTime() ; val result = block ; val t1 = System.nanoTime() ; println("Elapsed time: " + (t1 - t0) + "ns") ; result ; } 
time: [R](block: => R)R 

scala> time { SCryptUtil.scrypt("dummy password 1", 2, 1, 1) } 
Elapsed time: 313823ns <-- 5 minutes 
res0: String = $s0$10101$2g6nrD0f5gDOTuP44f0mKg==$kqEe4TWSFXwtwGy3YgmIcqAhDvjMS89acST7cwPf/n4= 

scala> time { SCryptUtil.scrypt("dummy password 1", 2, 1, 1) } 
Elapsed time: 178461ns 
res1: String = $s0$10101$C0iGNvfP+ywAxDS0ARoqVw==$k60w5Jpdt28PHGKT0ypByPocCyJISrq+T1XwmPlHR5w= 

scala> time { SCryptUtil.scrypt("dummy password 1", 65536, 8, 1) } 
Elapsed time: 130900544ns <-- 0.1 seconds 
res2: String = $s0$100801$UMTfIuBRY6lO1asECmVNYg==$b8i7GABgeczVHKVssJ8c2M7Z011u0TMBtVF4VSRohKI= 

scala> 313823L/1e9 
res3: Double = 313.823

scala> 130900544L/1e9 
res4: Double = 0.130900544 

注意:這與Docker無關。我只是在Docker外面測試,openjdk 8直接安裝在GCE實例上,結果是一樣的:scrypt(..)第一次需要3分鐘,但CPU空閒90-100%。此後,請求立即完成scrypt。

回答

1

問題是播種隨機數發生器需要很長時間。 Scrypt做到這一點:

public static String scrypt(String passwd, int N, int r, int p) { 
    try { 
     byte[] salt = new byte[16]; 
     SecureRandom.getInstance("SHA1PRNG").nextBytes(salt); <--- look 

     byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32); 

here

nextBytes(salt)調用導致SecureRandom對象爲其自身提供種子,這需要長達一個半小時,我的谷歌Compute Engine的實例。

這是不相關的Java或碼頭工人,而是看這裏:(直接在主機上,沒有任何多克爾容器內)

# < /dev/random stdbuf -o0 strings --bytes 1 | stdbuf -o0 tr -d '\n\t ' 

這從/ dev /隨機讀取隨機字符,和我現在已經運行了幾分鐘,但在幾分鐘後,目前只輸出了3個字符。所以它超級慢。

使用隨機的,但速度更快,的/ dev/urandom的替代,那麼這樣的:

# < /dev/urandom stdbuf -o0 strings --bytes 1 | stdbuf -o0 tr -d '\n\t ' 

打印99999個字符立即。

(我發現了上述< /dev/random ...命令在這裏:https://unix.stackexchange.com/a/114883/128585

在我的筆記本電腦,雖然,/dev/random/版本立即打印字符30-40。然後它會阻止,並且每隔10秒左右打印一個或幾個字符。當我使用鼠標或鍵盤或網絡時,它可能會隨機發生。


更新

我做了什麼:我現在正在使用/dev/urandom代替 - 據我讀過在互聯網上,這是完全罰款。

而且我也開始使用硬件隨機數發生器;顯然GCE實例有這些。

apt install rng-tools # start using any hardware rand num gen, on Ubuntu 
相關問題