背景

我们有一个作业系统质检的功能,功能大概就是从作业中,进行随机抽取。随机抽取的功能中,使用到了随机数的功能。

这个作业系统会出现启动一段时间后,就突然出现大量线程阻塞,导致整个应用超时无响应。

问题分析

jstack后,如下是大量线程信息,基本所有线程都是如下:

java.lang.Thread.State: BLOCKED (on object monitor)
at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(NativePRNG.java:201)
- waiting to lock <....0xABCSDESDASDSA> (a java.lang.Object)
at sun.security.provider.NativePRNG$RandomIO.access$300(NativePRNG.java:108)
at sun.security.provider.NativePRNG.engineGenerateSeed(NativePRNG.java:102)
at java.security.SecureRandom.generateSeed(SecureRandom.java:495)

通过简单查询,发现遇到这个问题的人还不是少数:

在Java中,SecureRandom.getInstanceStrong()是用于获取安全的随机数生成器(SecureRandom)的方法。它使用强安全性提供者(Strong Security Provider)来获取一个实例,这通常是操作系统提供的本地安全实现。虽然这种方法提供了高质量的随机数生成器,但它有时可能导致系统响应变慢的问题。

这种性能问题的主要原因是,SecureRandom.getInstanceStrong()可能需要在获取强安全性提供者的过程中进行耗时的操作,例如系统调用或硬件生成的随机数。这可能导致在某些情况下,特别是在具有低熵(entropy)的系统环境中,调用此方法时会出现较长的延迟。

注意,这里出现了一个新概念(至少对我来说是):熵。

Linux内核采用熵来描述数据的随机性,熵(entropy)是描述系统混乱无序程度的物理量,一个系统的熵越大则说明该系统的有序性越差,即不确定性越大。内核维护了一个熵池用来收集来自设备驱动程序和其它来源的环境噪音。

Linux系统中,随机数是通过/dev/random/dev/urandom这两个特殊的设备文件提供的。这些设备文件允许用户和应用程序获取系统的随机数据。这些设备文件利用系统的熵源(entropy source)来生成随机数,其中熵源是系统中的不可预测事件,如硬件中断、鼠标移动、键盘输入等。

以下是/dev/random/dev/urandom的主要区别以及关于它们的一些信息:

  • /dev/random /dev/random是一个阻塞设备,它会等待足够的熵积累后再提供输出。如果熵源不够,读取/dev/random的操作可能会被阻塞,直到有足够的熵可用。这是为了确保生成的随机数具有更高的质量,因为/dev/random提供的是真正的随机数。
  • /dev/urandom /dev/urandom是一个非阻塞设备,它会立即提供随机数输出,即使熵源不足。当系统的熵源不足时,/dev/urandom仍然会生成伪随机数。这样,即使熵源不够,应用程序仍然能够获得随机数,但可能会牺牲一些质量。

问题解决

方式一:提高系统的熵

提高系统的熵是为了增加随机性,从而提高随机数生成的质量。系统的熵源(entropy source)是生成随机数的基础,因此增加熵源可以提高系统的熵。以下是一些方法可以提高系统的熵:

  1. 使用硬件随机数生成器: 如果系统支持硬件随机数生成器,可以利用它来提供高质量的随机性。硬件随机数生成器基于物理过程,如电子噪声、热噪声等,因此提供了真正的随机性。
  2. 收集更多的外部数据: 增加从外部源收集的数据,如鼠标移动、键盘输入、网络流量等,以增加系统的熵。这些事件通常是不可预测的,可以用作随机性的来源。
  3. 使用定时器和事件: 利用系统的定时器和事件来增加熵。例如,利用定时器中断、磁盘访问时间等事件,这些都可以提供一些额外的随机性。
  4. 使用环境噪声: 利用环境中的噪声,如温度变化、大气压力、光照变化等,作为熵源。这些物理变化通常是不可预测的。
  5. 混合多个熵源: 将多个熵源混合在一起,以增加总体熵。通过组合多个不同的熵源,可以提高整体的随机性。

在Linux系统中,可以使用以下命令查看系统熵的信息:

cat /proc/sys/kernel/random/entropy_avail

由于虚拟机环境的特殊性,虚拟机中生成的随机数可能不具备与物理环境相同的高质量随机性。在一些对高质量随机数要求较高的安全应用中,建议考虑使用真实硬件的随机数生成器,或者通过其他手段确保随机性。

方式二:使用/dev/urandom

在Java中,如果你想确保使用 /dev/urandom 设备来生成随机数,可以使用以下方式:

SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(new byte[1]);  // 强制初始化,确保使用 /dev/urandom

// 生成随机数
byte[] randomBytes = new byte[32];  // 例如,生成32字节的随机数
secureRandom.nextBytes(randomBytes);
// 此时 secureRandom 使用的应该是 /dev/urandom

请注意,这是一种相对可靠的方式,但并不是绝对的,因为具体的提供者和实现可能因Java版本和配置而变化。最好查看你使用的Java版本的文档来了解详细信息。

如果你希望更精确地控制随机数生成的算法和提供者,你可以使用明确的算法和提供者名称,例如:

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");

方式三:使用伪随机数

比如:RandomUtils

RandomUtils 是Apache Commons Lang库中的一个工具类,它提供了一些用于生成随机数的方法。具体来说,RandomUtils使用的是java.util.Random类,而不是SecureRandom

java.util.Random 类是Java标准库中提供的随机数生成器,它使用伪随机算法,因此生成的随机数序列是可预测的,不适用于一些安全性要求较高的场景。在一些需要更高质量随机数的情况下,推荐使用 SecureRandom 类,它能够提供更安全、更随机的随机数。

还有个大坑

如上图,sonar扫描的时候会使用 Random 有问题,不够安全。会建议使用SecureRandom.getInstanceStrong() ,如果对他不够熟悉,就会导致线上问题啦!

最后修改日期: 2024年2月6日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。