网站首页 > java教程 正文
本文预计阅读时间:10分钟
最近开发了一个新需求,要求对项目做压测,很奇怪,单机达到20万QPS之后就怎么也上不去了,增加线程之后,性能反而下降的厉害。经过一番分析,发现处理线程会block在UUID的一个地方,跟踪源码才发现了这个大坑。
先来介绍下它吧,UUID (Universally Unique Identifier) 大家都很熟悉,它是由一组32位数的16进制数字所构成,采用如下编码规则
1-8位采用系统时间,在系统时间上精确到毫秒级保证时间上的惟一性;9-16位采用底层的IP地址,在服务器集群中的惟一性;17-24位采用当前对象的HashCode值,在一个内部对象上的惟一性;25-32位采用调用方法的一个随机数,在一个对象内的毫秒级的惟一性。
通过以上4种策略能够保证在整个分布式系统中的惟一性。
使用非常简单,Java提供了简易的api。
public String createUUID() { UUID uuid = UUID.randomUUID(); return uuid.toString(); }
但是,就是这短短的两句,成为了系统的性能瓶颈。
我们先来看下压测。。
机器配置
CPU: 16核 2.20GHz Memory: 16G JDK: 1.8 VM:?CentOS?6 GC: G1
测试代码
while (true) { UUID.randomUUID(); }
压测结果
很奇怪,常理来说,增大线程数都会带来性能的提升,但是在UUID这里行不通了。使用jstack发现,线程都block在了这里
test-thread BLOCKED blocked on java.security.SecureRandom@3b194842 owned by "test-thread" Id=358 at java.security.SecureRandom.nextBytes(SecureRandom.java:468) at java.util.UUID.randomUUID(UUID.java:145) ..... at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:617) at java.lang.Thread.run (Thread.java:745)
看到这里,基本已经知道问题了,UUID.randomUUID()是一个静态方法,内部使用一个静态成员变量SecureRandom,在SecureRandom内部有一个方法级别的锁,所以在锁竞争非常强烈的时候性能会下降的特别厉害。
public static UUID randomUUID() { SecureRandom ng = Holder.numberGenerator; byte[] randomBytes = new byte[16]; ng.nextBytes(randomBytes); randomBytes[6] &= 0x0f; /* clear version */ randomBytes[6] |= 0x40; /* set to version 4 */ randomBytes[8] &= 0x3f; /* clear variant */ randomBytes[8] |= 0x80; /* set to IETF variant */ return new UUID(randomBytes); }
我们再看下SecureRandom的Java doc
* Note: Depending on the implementation, the {@code generateSeed} and * {@code nextBytes} methods may block as entropy is being gathered, * for example, if they need to read from /dev/random on various Unix-like * operating systems.
意思是SecureRandom的generateSeed和nextBytes这两个方法可能会block,依赖随机数的产生,如果随机数不够了,它有可能就会堵塞在那边。比如随机数的产生是读取unix类系统的/dev/random文件。为什么是比如呢?事实上SecureRandom是使用SPI做扩展的。
public void nextBytes(byte[] bytes) { secureRandomSpi.engineNextBytes(bytes); }
那么/dev/random又是什么呢?Unix-Like或者Linux系统有两个随机伪设备:/dev/random和/dev/urandom,他们提供永不为空的随机字节数据流。/dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。
/dev/random的random pool依赖于系统中断,因此在系统的中断数不足时,/dev/random设备会一直封锁,尝试读取的进程就会进入等待状态,直到系统的中断数充分够用, /dev/random设备可以保证数据的随机性。
而/dev/urandom不依赖系统的中断,也就不会造成进程忙等待,但是数据的随机性比/dev/random低,在不是对随机性要求特别高的场景下,可以提供更高的性能
在java启动项中增加-Djava.security.egd=file:/dev/./urandom 配置项之后,再测试一下, 性能提升了1.5倍。
某团基础架构部搬砖工,专注于高并发、高可靠系统研发。本公号主要素材来自于个人日常工作、思考,偶尔也有前沿新闻、国外译文。关注我就对了= =
往期文章:
猜你喜欢
- 2024-10-27 5种全局ID生成方式、优缺点及改进方案
- 2024-10-27 分布式系统怎么快速生成分布式唯一ID?
- 2024-10-27 Java 中分布式 ID 的设计方案(java分布式的优点)
- 2024-10-27 Nanoid - 可能会取代 UUID 的ID产生器
- 2024-10-27 如何生成分布式ID(分布式生成唯一id的生成方式)
- 2024-10-27 神秘的UUID(神秘的反义词)
- 2024-10-27 优雅的数据库ID设计(数据库 id)
- 2024-10-27 Java的ID生成策略(java实现id自增)
- 2024-10-27 9种分布式ID生成方式,总有一款适合你
- 2024-10-27 在分布式系统中,如何生成分布式ID?
你 发表评论:
欢迎- 05-16SpringBoot整合Redis实现常用功能
- 05-16基于Redis实现简单的延时消息队列
- 05-16安装Redis
- 05-16Spring系列之Redis的两种集成方式
- 05-16Django连接Redis集群问题排查思路和总结
- 05-16只需5分钟,完成Redis所有命令操作~
- 05-16熟练使用 Redis 的 5 大数据结构:Java 实战教程
- 05-16Redis 常见业务场景及实例(Java)
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)