网站首页 > 技术文章 正文
本文预计阅读时间: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倍。
某团基础架构部搬砖工,专注于高并发、高可靠系统研发。本公号主要素材来自于个人日常工作、思考,偶尔也有前沿新闻、国外译文。关注我就对了= =
往期文章:
- 上一篇: 反射java调用方法_java 使用反射调用方法
- 下一篇: java 如何一行代码生成随机数据?
猜你喜欢
- 2024-12-13 Set的用法和实例详解——Java进阶知识讲义系列(五)
- 2024-12-13 阿里Java三面:分布式延时任务方案解析,万字长文一篇点通你
- 2024-12-13 Java快速处理图片的方式(总结)
- 2024-12-13 Java八股文面试全套真题【含答案】-反射篇
- 2024-12-13 java 如何一行代码生成随机数据?
- 2024-12-13 反射java调用方法_java 使用反射调用方法
- 2024-12-13 详解 Java 中的变量
- 2024-12-13 Java面试题:Java代理的几种实现方式?
- 2024-12-13 详解Java中的静态代理和动态代理
- 2024-12-13 9分钟带你搞懂代理模式、静态代理、JDK+CGLIB动态代理
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)