使用 JFR 排查 SSL 性能瓶颈#
在某个时间点,我们的微服务的一个实例突然经历了 CPU 使用率的急剧飙升:

同时,我们注意到建立了异常数量的数据库连接:
有趣的是,其他实例根本没有显示这种行为。
根本原因调查#
考虑到大量的数据库连接,我们最初的假设是数据库可能表现不佳。然而,当我们检查这个时间段内的 SQL 统计时,数据库性能看起来完全正常:

在此期间,我们微服务的热点 SQL 查询执行时没有任何明显的延迟。那么是什么可能导致这个问题?我们考虑了几种可能性:垃圾收集开销、safepoint 操作或长时间获取锁(更多详细信息,请查看:使用 JFR 进行 Java 监控完整指南)。为了彻底解决这个问题,我们决定捕获 JFR 转储并分析 safepoint 事件、GC 活动和 Monitor Blocked 事件。
我们的第一站是检查 GC 行为。我们发现所有垃圾收集都是 Young GC 事件,暂停时间可接受:

接下来,我们查看了 safepoint 操作。虽然我们确实捕获了一些 safepoint 事件,但它们的暂停持续时间并不特别令人担忧:

最后,我们调查了 Java Monitor Blocks,发现了一些有趣的事情 - 大量长时间锁等待的实例:

堆栈跟踪显示线程在以下位置被阻塞:void sun.security.provider.SecureRandom.engineNextBytes(byte[])。这指向了一个与随机数生成相关的经典 Java 性能问题。查看 NativePRNG 中的相关代码:
// name of the *System* property, takes precedence over PROP_RNDSOURCE
private static final String PROP_EGD = "java.security.egd";
// name of the *Security* property
private static final String PROP_RNDSOURCE = "securerandom.source";
private static final boolean useLegacyDSA =
Boolean.parseBoolean(GetPropertyAction.privilegedGetProperty
("jdk.security.legacyDSAKeyPairGenerator"));
static final String URL_DEV_RANDOM = "file:/dev/random";
static final String URL_DEV_URANDOM = "file:/dev/urandom";
这涉及两种不同的生成随机数种子的方法:“file:/dev/random” 和 “file:/dev/urandom”。你可以通过设置系统属性 java.security.egd 来指定使用哪一个,默认选择是 “file:/dev/random”。
理解随机数生成和解决方案#
在 Linux 4.8 之前:

从 Linux 4.8 开始:

关键洞察是:当熵池不足时,默认的 “file:/dev/random” 将阻塞并等待,而 “file:/dev/urandom” 继续运行而不阻塞。对于我们的用例,“file:/dev/urandom” 提供了足够的随机性,因此我们可以通过设置系统属性 -Djava.security.egd=file:/dev/./urandom 来使用 urandom 并消除阻塞行为来解决这个问题。
