2016-05-14 86 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 
相关问题