点击上方蓝色“程序猿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性能监控等可视化监控平台帮助真的很大,否则到处去查日志得查的晕头转向了。

经验总结

  1. 接口设计,需要避免超大响应体出现,分而治之,将一个大接口拆分为多个小接口。

  2. 缓存设计,像这个服务一样,一个请求带来将近40次DB查询的,需要考虑在服务端进行缓存(当时偷懒了,要求调用方去做缓存)。

  3. 性能设计,要对自己负责系统的性能了如指掌,可以通过压测等手段得到自己系统的天花板,否则,某一个接口hang住,会导致整个应用的可用性出现问题。

  4. 流量隔离,内部应用和外部流量之间,需要进行流量隔离,即使通过缓存,也有缓存击穿的问题。

  5. 口头说的东西都不靠谱,要落在文档上,还需要检查执行情况。

往期推荐

只需4步,自己搞个 Spring Boot Starter !

ES 查询数据的工作原理是什么?

说说你知道的数据库常用架构方案?

ArrayList 为什么要实现 RandomAccess 接口?

为什么阿里规定事务注解@Transactional中指定rollbackFor?

扫一扫,关注我

一起学习,一起进步

年轻代频繁ParNew GC,导致http服务rt飙高相关推荐

  1. java年轻代频繁gc_年轻代频繁ParNew GC,导致http服务rt飙高

    背景介绍 某日下午大约四点多,接到合作方消息,线上环境,我这边维护的某http服务突然大量超时(对方超时时间设置为300ms),我迅速到鹰眼平台开启采样,发现该服务平均QPS到了120左右,平均RT在 ...

  2. httpclient默认配置导致rt飙高

    功能上线后,运行平稳过一段时间(也许新功能上线后,进行了灰度,访问量不大)也就没引起注意.事故发生后,也立即做了补救措施,但部分现场也没有及时保留,从告警日志中发现,rt飙高的接口都指向了同一个接口. ...

  3. java年轻代频繁gc_JVM young GC频繁,内存泄漏

    young gc频繁,老年代占用线性上涨 young gc频繁因为产生系统使用log4j1版本在系统中大量使用debug日志输入,导致频繁创建Stringbuilder对象,然后导致年轻代空间不够,执 ...

  4. java年轻代频繁gc_JVM 年轻代 老年代 持久代 gc

    虚拟机中的共划分为三个代:年轻代(Young Generation).老年代(Old Generation)和持久代(Permanent Generation).其中持久代主要存放的是Java类的类信 ...

  5. json.tojsonstring 导致cpu飙高_阿里调试神器立功了!进程导致Kubernetes节点CPU飙高的排查与解决...

    来源:https://www.cnblogs.com/maxzhang1985/p/12673160.html 一.发现问题 在一次系统上线后,我们发现某几个节点在长时间运行后会出现CPU持续飙升的问 ...

  6. 面试官:线上服务CPU飙高怎么排查?

    用jstack排查 先执行top,找到CPU占用比较高的进程 jstack 进程id > show.txt 找到进程中CPU占用比较高的线程,线程id转为16进制 到show.txt文件中根据线 ...

  7. java虚拟机年轻代的gc

    垃圾分贷收集 当前商业虚拟机的垃圾收集都采用"分代收集"(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为 ...

  8. Java 年轻代GC

    JVM的年轻代 为什么会有年轻代 我们先来想想,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块, ...

  9. JVM专题之分代模型:年轻代、老年代、永久代

    目录 一 什么是GC 分代 二 GC 为什么需要分代 三 GC 如何分代,每一个代具体是怎么工作的 3.1 年轻代 3.1.1 Eden Space 3.1.2 Survivor 3.2 老年代 3. ...

最新文章

  1. FastQC结果解读
  2. Android SDK版本号 与 API Level 对应关系
  3. 邻域闭包matlab,闭包 - it610.com
  4. 【转载】目前为止看到描述VSCode编写C++配置文件最清楚的一篇文章
  5. 获取JavaScript变量的类型
  6. Angular应用双向绑定的语法糖
  7. 【数据分析学习】线性降维方法
  8. Linux GRUB 引导Win 7 ---- error: invalid EFI file path
  9. 运用事理图谱搞事情:新闻预警、事件监测、文本可视化、出行规划与历时事件流生成
  10. apirestful php自动测试,PHP实现自动识别Restful API的返回内容类型
  11. 嵌入式操作系统内核原理和开发(头文件调整)
  12. 广东女子职业技术学院计算机应用技术,广东女子学院虚拟校园系统的构建与技术实现...
  13. 已解决 | burp无法抓取iphone https数据包
  14. 「解决方案」运维、能耗、网关整体解决方案
  15. 02-SpringBoot集成MinIo
  16. 掌握这几款高保真设计软件,百万年薪不再是梦想
  17. 服务器遇到Broken Pipe崩溃
  18. 删除只读属性的文件夹及其子文件
  19. 获取硬盘序列号的真正方法!!
  20. 人生观价值观与老生常谈

热门文章

  1. linux 结构体 struct addrinfo 简介
  2. linux c 文件指针 句柄(FILE*) 文件描述符(fd) 文件路径(filepath) 互相转换
  3. linux avahi-daemon进程 网络服务 简介
  4. golang 文件服务器 实现
  5. golang slice 切片 追加、删除、插入
  6. 解决工控网络通信协议威胁的实践
  7. Linux2.6 --系统调用处理程序
  8. linux 将文件分成两部分,linux 将大文件分成小文件
  9. createinstance.java_C# Activator.CreateInstance()方法使用
  10. 问题集锦(16-20)