神秘的偶发服务超时,原因可能是那些坏邻居

栏目: Java · 发布时间: 5年前

内容简介:唯品会在服务化体系改造的初期,一个对延时敏感的应用,偶然会发生一些超时,事发当时zabbix分钟级监控,dstat秒级监控的服务器指标都正常,应用,数据库,缓存,网络也正常,那这是为什么呢?某天脑洞大开,把怀疑的目光投向了在后台运行日志收集程序Flume,发现它的GC运行得比较狂野,于是对它的GC线程数做了限制:修改前:15分钟内,  大于30ms的业务调用173次,  大于50ms的23次

1. 恶邻A君

唯品会在服务化体系改造的初期,一个对延时敏感的应用,偶然会发生一些超时,事发当时zabbix分钟级监控,dstat秒级监控的服务器指标都正常,应用,数据库,缓存,网络也正常,那这是为什么呢? 

某天脑洞大开,把怀疑的目光投向了在后台运行日志收集程序Flume,发现它的GC运行得比较狂野,于是对它的GC线程数做了限制:

修改前:15分钟内,  大于30ms的业务调用173次,  大于50ms的23次
修改后: 246分钟内,大于30ms的业务调用41次,   大于50ms的4次

2. 恶邻B君

又过了若干个月,又有某些应用,又开始抽风。这次相对好查一些,因为我们新升级了服务器的监控系统,只要在两台机器上做一下对比测试就好了。 只花了一个晚上,基本就能验明凶手了。

那这个新升级的监控系统,又是怎么影响到主应用的呢?找出它与应用有交互的部分,原来对于JVM的各种线程数信息,堆内存各代的信息,每拿一个数据都会启动一次JMX Client,所以每分钟都有一秒要连拿7个数据,启动7个JMX Client。

改进方法很简单,我们自己定制了一下JMX Client,将7个数据合并在一个命令里获得,另外定制了一下JMX Client的JVM参数,将它启动的动静尽量减少。

3. 逆优化

可见, JVM是个运行服务端应用的好VM,但体量有点大。 如果你只是想频繁地运行一段 Java 写的脚本,或者在跑一些辅助性的程序比如监控和日志收集,往常推荐的JVM参数也就不再合适里,需要进行逆优化才能做个安静的好邻居:

一、启动快速,动静小。

二、低成本,节约CPU、内存和线程。

三、低扰动,不干扰主应用的运行。

4. 从失败的取经开始

第一时间,觉得和JDK自带的jmap,jstack们用一样的参数就好了,多简单。

在它们运行时,跑 jps -v   , 结果发现通通只有一个-Xms8m 。

还不死心,又 去翻源码 ,JDK7在  Makefile.launcher ,JDK8在 CompileLaunchers.gmk 结果发现全部8M,通通8M,再没别的参数了。

有同学又从久远的记忆中想起一个-client,感觉也是比较弱气的选项,但在这个多核的64位 Linux 服务器上是根本无效的,一定是-server,必须是-server。

5. 逆优化的思路

JVM与上述诉求相冲的几个地方:

  • 各种吃内存

  • 各种后台线程

  • JIT时CPU表现狂野

  • GC时CPU表现狂野

那我们就从这几个方面着手。

在开始折腾前,先准备好测试手段:

首先,给 工具 脚本配上GC 日志参数,在GC日志里就能看到实际启动参数,GC纪录,以及运行结束时内存各代的占用。

-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime

其次,长期跑一个 pidstat -l 1| grep xxx ,紧密监控进程的CPU消耗。

最后,jstack看线程。

6. 类的加载和编译优化

6.1 -Xverify:none

来自优化Eclipse启动速度的经验,说关闭Java类加载验证可以加快10% -15%的启动速度,嗯,好,加。

6.2 设定编译级别

JIT编译之后的代码比解释执行字节码更快,更省CPU。比如vjtools里的vjtop,没编译时每回运行要50%单核CPU,75ms执行完一个探测循环,而编译后10%单核,15ms就能完工。

但编译本身就需要CPU,也需要额外的编译线程。

如果脚本只简单的跑一次,比如vjtools里的vjmxcli,建议就不要进行JIT编译了,编译完了也用不上,直接解释执行就好。禁止它: -Djava.compiler=NONE

如果脚本用于密集计算,比如vjtools里的vjmap,则建议打开多层编译,一开始就对运行到的方法进行静态编译,不用等方法被调用1万次。多层编译在JDK8默认打开,显式打开: -XX:+TieredCompilation

但打开多层编译也会导致程序运行初期有较多的编译任务,吃比较多的CPU,可以显式关掉多层编译 -XX:-TieredCompilation 来对比一下,综合其带来的性能提升,脚本的运行时间的长短,以及额外的CPU支出来综合评价。

6.3 编译线程的设定

在24核服务器上,默认有4条C1编译线程,8条C2编译线程(多层编译下),可以把它设到最小的 -XX:CICompilerCount=2

6.4 未来黑科技-AOT

JIT真的不适合脚本,还是预先把代码编译(Ahead-of-Time,AOT) 更好。 JDK9里有一个Hotspot编译器组搞的试验性的jaotc,另一个选择是GraalVM全家桶里带的SubstrateVM,支持JDK8。

看各位大大炫,但我还没玩过。

7. GC 优化

脚本们一般不介意GC延时,建议使用吞度量最的串行收集算法 -XX:+UseSerialGC ,避免了其他GC算法所需的大量GC线程,更绝对保证了自己GC时不会影响到主应用。

如果依然想使用并行算法,就一定要设置GC线程数,在24核机器上YGC和CMS GC的线程数默认分别是18和5,为了避免成为恶邻A君。可设为:

-XX:ParallelGCThreads=4 -XX:ConcGCThreads=2

8. 内存优化

首先,JVM的堆内存

一,默认的JVM初始内存大小,在大内存的服务器上会比较大,必须设置。

二, -Xms 与 -Xmx 不等时,  自动扩张并没有想象中那么智能和合理。

三、新生代默认只有1/3堆大小,而在脚本看来新生代才是大头。

建议根据GC日志的结果,完整设置-Xms 和 -Xmx,并用-Xmn(新生代占大头) 或-XX:NewRatio=1(一半半) 来设置新生代大小。

其次,每条线程的内存,从默认1M回到256k: -xss256k

其他永久代,CodeCache的初始值还算合理,没看到特别浪费的情况不用管。

9. 小结

想起写这个,主要是看杨晓峰的《Java核心技术36讲》里的某几篇挑起的兴趣。

神秘的偶发服务超时,原因可能是那些坏邻居

一段快速执行,关闭了JIT的脚本,启动命令大概长这个样子:

-Xms96m -Xmx96m -Xmn64m -Xss256k -XX:+UseSerialGC -Djava.compiler=NONE -Xverify:none -XX:AutoBoxCacheMax=20000

一段辅助程序,启动命令大概长这个样子

-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:-TieredCompilation -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000

这篇的原主题是《快速,低成本,低扰动地运行一段Java代码》,以下配图刚好:

神秘的偶发服务超时,原因可能是那些坏邻居

神秘的偶发服务超时,原因可能是那些坏邻居


以上所述就是小编给大家介绍的《神秘的偶发服务超时,原因可能是那些坏邻居》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数学拾遗

数学拾遗

加黑蒂 / 清华大学出版社 / 2004-8 / 49.00元

Beginning graduate students in mathematics and other quantitative subjects are expected to have a daunting breadth of mathematical knowledge ,but few have such a backgroud .This book will help stedent......一起来看看 《数学拾遗》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具