年轻代频繁ParNew GC,导致http服务rt飙高
点击上方蓝色“程序猿DD”,选择“设为星标”
回复“资源”获取独家整理的学习资料!
作者 | 北交吴志炜
来源 | https://www.jianshu.com/p/e4d70be7fc1e
背景介绍
某日下午大约四点多,接到合作方消息,线上环境,我这边维护的某http服务突然大量超时(对方超时时间设置为300ms),我迅速到鹰眼平台开启采样,发现该服务平均QPS到了120左右,平均RT在2秒多到3秒,部分毛刺高达5到6秒(正常时候在60ms左右)。
qps情况
rt情况
问题解决
该服务是一个对内的运营平台服务(只部署了两台docker)预期qps个位数,近期没做过任何的线上发布,核心操作是整合查询数据库,一次请求最多涉及40次左右的DB查询,最终查询结果为一个多层树形结构,一个响应体大约50K。之前口头跟调用方约定要做缓存,现在看到QPS在120左右,(QPS证明没有做缓存),遂要求对方做缓存,降低QPS。后QPS降到80以内,rt恢复正常(平均60ms),最终QPS一直降到40(后续需要推动调用方上缓存,保证QPS在个位数)。
问题定位
由于该服务核心操作是查询数据库,且一次请求有40次DB query,遂首先排查是否慢sql导致,查看db性能监控,发现db 平均rt在0.3ms以内,可以算出来DB整体耗时在12ms左右,排除慢sql导致RT变高。
开始怀疑,是否DB连接池在高并发下出现排队,tddl默认的连接池大小是10.一查监控,整个占用的连接数从来没有超过7个,排除连接池不足的问题
至此,造成RT高的原因,在数据库层面被排除。
接着开始查采样到的服务调用链上的每一个执行点,看看到底是调用链上的那部分耗时最多。发现里面很多执行点都有一个特点,就是本地调用耗时特别长(几百毫秒),但是真正的服务调用(比如db查询动作)时间却很短,(0ms代表执行时间小于1ms,也间接印证之前db的平均RT在0.3ms以内)
本地调用耗时: 267ms
客户端发送请求: 0ms
服务端处理请求: 0ms
客户端收到响应: 1ms
总耗时: 1ms
这时候问题逐渐清晰,问题出现在本地方法执行的耗时过长,可是再次检查该服务所有代码,并没有需要长耗时的本地执行逻辑,那么继续看CPU的load情况
load长时间在4左右徘徊,我们的docker部署在4c8G的宿主机上,但是我们不能独占这个4C的,持续这么高的load已经不正常了。继续追查cpu load飙高的原因,接着去看GC日志,发现大量的Allocation Failure,然后ParNew次数在每分钟100次以上,明显异常,见下GC日志例子
2020-03-25T16:16:18.390+0800: 1294233.934: [GC (Allocation Failure) 2020-03-25T16:16:18.391+0800: 1294233.935: [ParNew: 1770060K->25950K(1922432K), 0.0317141 secs] 2105763K->361653K(4019584K), 0.0323010 secs] [Times: user=0.12 sys=0.00, real=0.04 secs]
每次占用cpu的时间在0.04s左右,但是由于ParNew GC太过频繁,每分钟最高100次以上,整体占用cpu时间还是很可观
看了下jvm内存参数
Heap Configuration:MinHeapFreeRatio = 40MaxHeapFreeRatio = 70MaxHeapSize = 4294967296 (4096.0MB)NewSize = 2147483648 (2048.0MB)MaxNewSize = 2147483648 (2048.0MB)OldSize = 2147483648 (2048.0MB)NewRatio = 2SurvivorRatio = 10MetaspaceSize = 268435456 (256.0MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 536870912 (512.0MB)G1HeapRegionSize = 0 (0.0MB)
年轻代分配了2G内存,其中eden区约1.7G
使用jmap查看年轻代对象占用空间情况,排名靠前的有多个org.apache.tomcat.util.buf包下的对象,比如ByteChunk、CharChunk、MessageBytes等,以及响应涉及的一些临时对象列表。其中ByteChunk等即tomcat响应输出相关类
至此问题明确,超大响应包(50K)在发送到网卡的过程中,需要经过从用户态user space拷贝到内核态 kernel space,然后在拷贝到网卡进行发送(像netty等的零拷贝针对的就是这种拷贝),加上响应体查询过程中,涉及的大量临时对象list,在高并发场景下,就导致年轻代内存占满,然后频繁gc(后续在合适的时间会压测该接口),这里还有一个点,很多人以为ParNewGC不会stop the world,其实是会的。频繁ParNewGC造成用户线程进入阻塞状态,让出CPU时间片,最终导致连接处理等待,接口的RT变高。整个排查过程,鹰眼,idb性能监控等可视化监控平台帮助真的很大,否则到处去查日志得查的晕头转向了。
经验总结
接口设计,需要避免超大响应体出现,分而治之,将一个大接口拆分为多个小接口。
缓存设计,像这个服务一样,一个请求带来将近40次DB查询的,需要考虑在服务端进行缓存(当时偷懒了,要求调用方去做缓存)。
性能设计,要对自己负责系统的性能了如指掌,可以通过压测等手段得到自己系统的天花板,否则,某一个接口hang住,会导致整个应用的可用性出现问题。
流量隔离,内部应用和外部流量之间,需要进行流量隔离,即使通过缓存,也有缓存击穿的问题。
口头说的东西都不靠谱,要落在文档上,还需要检查执行情况。
往期推荐
只需4步,自己搞个 Spring Boot Starter !
ES 查询数据的工作原理是什么?
说说你知道的数据库常用架构方案?
ArrayList 为什么要实现 RandomAccess 接口?
为什么阿里规定事务注解@Transactional中指定rollbackFor?
扫一扫,关注我
一起学习,一起进步
年轻代频繁ParNew GC,导致http服务rt飙高相关推荐
- java年轻代频繁gc_年轻代频繁ParNew GC,导致http服务rt飙高
背景介绍 某日下午大约四点多,接到合作方消息,线上环境,我这边维护的某http服务突然大量超时(对方超时时间设置为300ms),我迅速到鹰眼平台开启采样,发现该服务平均QPS到了120左右,平均RT在 ...
- httpclient默认配置导致rt飙高
功能上线后,运行平稳过一段时间(也许新功能上线后,进行了灰度,访问量不大)也就没引起注意.事故发生后,也立即做了补救措施,但部分现场也没有及时保留,从告警日志中发现,rt飙高的接口都指向了同一个接口. ...
- java年轻代频繁gc_JVM young GC频繁,内存泄漏
young gc频繁,老年代占用线性上涨 young gc频繁因为产生系统使用log4j1版本在系统中大量使用debug日志输入,导致频繁创建Stringbuilder对象,然后导致年轻代空间不够,执 ...
- java年轻代频繁gc_JVM 年轻代 老年代 持久代 gc
虚拟机中的共划分为三个代:年轻代(Young Generation).老年代(Old Generation)和持久代(Permanent Generation).其中持久代主要存放的是Java类的类信 ...
- json.tojsonstring 导致cpu飙高_阿里调试神器立功了!进程导致Kubernetes节点CPU飙高的排查与解决...
来源:https://www.cnblogs.com/maxzhang1985/p/12673160.html 一.发现问题 在一次系统上线后,我们发现某几个节点在长时间运行后会出现CPU持续飙升的问 ...
- 面试官:线上服务CPU飙高怎么排查?
用jstack排查 先执行top,找到CPU占用比较高的进程 jstack 进程id > show.txt 找到进程中CPU占用比较高的线程,线程id转为16进制 到show.txt文件中根据线 ...
- java虚拟机年轻代的gc
垃圾分贷收集 当前商业虚拟机的垃圾收集都采用"分代收集"(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为 ...
- Java 年轻代GC
JVM的年轻代 为什么会有年轻代 我们先来想想,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块, ...
- JVM专题之分代模型:年轻代、老年代、永久代
目录 一 什么是GC 分代 二 GC 为什么需要分代 三 GC 如何分代,每一个代具体是怎么工作的 3.1 年轻代 3.1.1 Eden Space 3.1.2 Survivor 3.2 老年代 3. ...
最新文章
- FastQC结果解读
- Android SDK版本号 与 API Level 对应关系
- 邻域闭包matlab,闭包 - it610.com
- 【转载】目前为止看到描述VSCode编写C++配置文件最清楚的一篇文章
- 获取JavaScript变量的类型
- Angular应用双向绑定的语法糖
- 【数据分析学习】线性降维方法
- Linux GRUB 引导Win 7 ---- error: invalid EFI file path
- 运用事理图谱搞事情:新闻预警、事件监测、文本可视化、出行规划与历时事件流生成
- apirestful php自动测试,PHP实现自动识别Restful API的返回内容类型
- 嵌入式操作系统内核原理和开发(头文件调整)
- 广东女子职业技术学院计算机应用技术,广东女子学院虚拟校园系统的构建与技术实现...
- 已解决 | burp无法抓取iphone https数据包
- 「解决方案」运维、能耗、网关整体解决方案
- 02-SpringBoot集成MinIo
- 掌握这几款高保真设计软件,百万年薪不再是梦想
- 服务器遇到Broken Pipe崩溃
- 删除只读属性的文件夹及其子文件
- 获取硬盘序列号的真正方法!!
- 人生观价值观与老生常谈
热门文章
- linux 结构体 struct addrinfo 简介
- linux c 文件指针 句柄(FILE*) 文件描述符(fd) 文件路径(filepath) 互相转换
- linux avahi-daemon进程 网络服务 简介
- golang 文件服务器 实现
- golang slice 切片 追加、删除、插入
- 解决工控网络通信协议威胁的实践
- Linux2.6 --系统调用处理程序
- linux 将文件分成两部分,linux 将大文件分成小文件
- createinstance.java_C# Activator.CreateInstance()方法使用
- 问题集锦(16-20)