为什么第一次使用scrypt()只需要1%的CPU,而在GCE中需要半个小时?
[总结&回答:显然问题是播种随机数发生器需要很长时间。请参阅下面的答案。 ]为什么第一次使用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。
问题是播种随机数发生器需要很长时间。 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