jvm - 为什么第一次调用 scrypt() 仅使用 1% CPU 并且在 GCE 中花费了半个小时?

标签 jvm google-compute-engine jit scrypt

[总结和答案:显然问题是随机数生成器需要很长时间才能生成种子。请参阅下面我的回答。 ]

在 Google 计算引擎 (GCE) 中,我的 Java 虚拟机应用向 scrypt 发出的第一个请求密码散列函数需要很长时间 - 我想是因为代码尚未即时编译。所以我正在热身 scrypt,通过制作 服务器启动时虚拟 scrypt("pswd", 2,1,1) 调用。然而,发生的情况是,CPU 上升到 300% 以上,并保持 10-20 秒,然后回落到 1%,尽管对 scrypt() 的请求尚未完成。现在,CPU 保持在 1%,持续很长时间(最多半小时,使用 2 个 GCE vCPU),直到最终 scrypt() 完成。

为什么会有这种奇怪的行为?

为什么 scrypt() 在完成之前不会继续以 300% CPU 运行?它不是 内存不足。请查看下面的 Docker 统计信息。

在第一个 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

如果你想在 Scala 和 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: 313823012366ns   <-- 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> 313823012366L / 1e9
res3: Double = 313.823012366

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

注意:这与 Docker 无关。我刚刚在 Docker 之外进行了测试,直接在 GCE 实例上安装了 openjdk 8,结果是相同的:scrypt(..) 第一次需要 3 分钟,但 CPU 为 90-100 % 闲置的。此后,加密请求立即完成。

最佳答案

问题是随机数生成器需要很长时间才能生成种子。 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 对象自行播种,这在我的 Google 计算引擎实例上最多需要半个小时。

这与 Java 或 Docker 无关,而是看这里:(直接在主机上,不在任何 Docker 容器内)

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

这会从/dev/random 读取随机字符,我已经让它运行了几分钟,但几分钟后,到目前为止只输出了 3 个字符。所以速度 super 慢。

使用随机性较低但速度更快的/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

关于jvm - 为什么第一次调用 scrypt() 仅使用 1% CPU 并且在 GCE 中花费了半个小时?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37223995/

相关文章:

google-app-engine - Google Push-To-Deploy 管道 - 单元测试因模块导入错误而失败

java - 带 JIT 和不带 JIT 的 JVM 之间的区别

python - 谷歌应用引擎如何正确部署应用程序

java - WatcherThread如何调用JVM监控例程?

java - 为什么 Eclipse 在 Windows 中使用 c :\WINDOWS\SYSTEM32\javaw. exe?

Java jvm 选项覆盖属性文件中的属性

mysql - 使用 cloudsql 在 Google Cloud 计算上构建网站

python - 由于数据类型,编译 njit nopython 版本的函数失败

c# - 预加载所有程序集 (JIT)

java.net.BindException :JVM Bind