UUID(Universally Unique Identifier,通用唯一标识符)是一种用于标识信息的128位标识符。Java开发人员倾向于使用 java.util.UUID#randomUUID
API来生成UUID编号(类似4c88314f-14ca-4652-8567-4471a0ef917c)。
UUID通常用于标识数据记录、会话、文件、对象等,以确保它们在不同上下文中的唯一性。注意,UUID是一种全局唯一性标识符,不保证在不同时间生成的UUID之间是有序的或可比较的,因此不应该依赖于UUID的大小或顺序。
在某些情况下,使用这个API可能对应用程序的可用性产生负面影响。下面,我们将通过一个实际案例来深入讨论这一问题。
randomUUID如何工作
java.util.UUID#randomUUID
API在内部使用操作系统中的entropy来生成一个唯一的数字。entropy是什么意思Linux内核使用某些技术,如用户的鼠标移动,硬件风扇噪音的变化,设备驱动程序噪音的变化,来生成随机数。当操作系统中缺乏熵时,随机数生成将减慢。当出现减速时,调用此 java.util.UUID#randomUUID
的应用程序线程将被置于BLOCKED状态,严重时会让程序处于暂停状态。
真实的世界应用程序-java.util.UUID#randomUUID()API中阻塞的50个线程
下面是一个应用程序的实际线程转储报告,该应用程序正遭受此问题的困扰。在线程转储报告中,我们可以注意到总共有102个线程。在这102个线程中,有50个线程由于java.util.UUID#randomUUID
API而处于BLOCKED状态。下面是这50个线程之一的堆栈跟踪:
"[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'" waiting for lock java.security.SecureRandom@20a56b2b BLOCKED
java.security.SecureRandom.nextBytes(SecureRandom.java:433)
java.util.UUID.randomUUID(UUID.java:159)
com.buggycompany.jtm.bp.<init>(bp.java:185)
com.buggycompany.jtm.a4.f(a4.java:94)
com.buggycompany.agent.trace.RootTracer.topComponentMethodBbuggycompanyin(RootTracer.java:439)
weblogicx.servlet.gzip.filter.GZIPFilter.doFilter(GZIPFilter.java)
weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3730)
weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3696)
weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2273)
weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2179)
weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1490)
weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
weblogic.work.ExecuteThread.run(ExecuteThread.java:221)
由于缺少 entropy,线程在调用 java.util.UUID#randomUUID
时进入了BLOCKED状态,无法继续执行代码。这50个线程被卡住了。
解决方案
JDK升级
这个问题是由Java中的一个已知bug引起的。但是,自JDK 8 u112或JDK 9 b105以来,它已被修复。所以最优先的解决方案就是升级你的JDK版本。
Linux安装Haveged
如果你的Java程序运行在Linux中,那么可以考虑安装haveged
库。haveged项目旨在提供一个易于使用的,不可预测的随机数生成器,基于HAVEGE算法的适应。这里是Haveged
项目GIT仓库页面。以下是如何安装它:
在基于Debian的平台(Debian,Ubuntu)上:
代码语言:javascript复制sudo apt-get install rng-tools
sudo update-rc.d haveged defaults
在Redhat平台(RHEL、Fedora、CentOS)上:
代码语言:javascript复制sudo yum install rng-tools
sudo chkconfig haveged on
用/dev/urandom代替/dev/random
类Unix操作系统提供了特殊的文件/dev/random
,用作伪随机数生成器。Java使用这个文件来生成随机数。可以将其配置为使用/dev/urandom
而不是/dev/random
。
/dev/urandom
是另一个能够生成随机数的特殊文件。然而,由于随机性较小,它具有降低安全性的缺点。如果需要的话,可以通过在启动过程中将下面的JVM参数传递给你的Java程序来实现它:
-Djava.security.egd=file:/dev/./urandom