生产事故!线上四台机器同一时间全部 OOM

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

案发现场

昨天晚上突然短信收到 APM (即 Application Performance Management 的简称),我们内部自己搭建了这样一套系统来对应用的性能、可靠性进行线上的监控和预警的一种机制)大量告警 画外音: 监控是一种非常重要的发现问题的手段,没有的话一定要及时建立哦

紧接着运维打来电话告知线上部署的四台机器全部 OOM (out of memory, 内存不足),服务全部不可用,赶紧查看问题!

问题排查

首先运维先重启了机器,保证线上服务可用,然后再仔细地看了下线上的日志,确实是因为 OOM 导致服务不可用

第一时间想到 dump 当时的内存状态,但由于为了让线上尽快恢复服务,运维重启了机器,导致无法 dump 出事发时的内存。所以我又看了下我们 APM 中对 JVM 的监控图表

画外音:一种方式不行,尝试另外的角度切入!再次强调,监控非常重要!完善的监控能还原当时的事发现场,方便定位问题。

不看不知道,一看吓一跳,从 16:00 开始应用中创建的线程居然每时每刻都在上升,一直到 3w 左右,重启后(蓝色箭头),线程也一直在不断增长),正常情况下的线程数是多少呢,600!问题找到了,应该是在下午 16:00 左右发了一段有问题的代码,导致线程一直在创建,且创建的线程一直未消亡!查看发布记录,发现发布记录只有这么一段可疑的代码 diff:在 HttpClient 初始化的时候额外加了一个 evictExpiredConnections 配置

问题定位了,应该是就是这个配置导致的!(线程上升的时间点和发布时间点完全吻合!),于是先把这个新加的配置给干掉上线,上线之后线程数果然恢复正常了。那 evictExpiredConnections做了什么导致线程数每时每刻在上升呢?这个配置又是为了解决什么问题而加上的呢?于是找到了相关同事来了解加这个配置的前因后果

还原事发经过

最近线上出现不少 NoHttpResponseException 的异常, 而上面那个配置就是为了解决这个异常而添加的,那是什么导致了这个异常呢?

在说这个问题之前我们得先了解一下 http 的 keep-alive 机制。

先看下正常的一个 TCP 连接的生命周期

可以看到每个 TCP 连接都要经过三次握手建立连接后才能发送数据,要经过四次挥手才能断开连接,如果每个 TCP 连接在 server 返回 response 后都立马断开,则发起多个 HTTP 请求就要多次创建断开 TCP, 这在 Http 请求很多的情况下无疑是很耗性能的, 如果在 server 返回 response 不立即断开 TCP 链接,而是复用这条链接进行下一次的 Http 请求,则无形中省略了很多创建 / 断开 TCP 的开销,性能上无疑会有很大提升。

如下图示,左图是不复用 TCP 发起多个 HTTP 请求的情况,右图是复用 TCP 的情况,可以看到发起三次 HTTP 请求,复用 TCP 的话可以省去两次建立 / 断开 TCP 的开销,理论上一个应用只要开启一个 TCP 连接即可,其他 HTTP 请求都可以复用这个 TCP 连接,这样 n 次 HTTP 请求可以省去 n-1 次创建 / 断开 TCP 的开销。这对性能的提升无疑是有巨大的帮助。

回过头来看 keep-alive (又称持久连接,连接复用)做的就是复用连接, 保证连接持久有效。

画外音: Http 1.1 之后 keep-alive 默认支持并开启,目前大部分网站都用了 http 1.1 了,也就是说大部分都默认支持连接复用了

天下没有免费的午餐 ,虽然 keep-alive 省去了很多不必要的握手/挥手操作,但由于连接长期保活,如果一直没有 http 请求的话,这条连接也就长期闲着了,会占用系统资源,有时反而会比复用连接带来更大的性能消耗。所以我们一般会为 keep-alive 设置一个 timeout, 这样如果连接在设置的 timeout 时间内一直处于空闲状态(未发生任何数据传输),经过 timeout 时间后,连接就会释放,就能节省系统开销。

看起来给 keep-alive 加 timeout 是完美了,但是又引入了新的问题(一波已平,一波又起!),考虑如下情况:

如果服务端关闭连接,发送 FIN 包(注:在设置的 timeout 时间内服务端如果一直未收到客户端的请求,服务端会主动发起带 FIN 标志的请求以断开连接释放资源),在这个 FIN 包发送但是还未到达客户端期间,客户端如果继续复用这个 TCP 连接发送 HTTP 请求报文的话,服务端会因为在四次挥手期间不接收报文而发送 RST 报文给客户端,客户端收到 RST 报文就会提示异常 (即 NoHttpResponseException)

我们再用流程图仔细梳理一下上述这种产生 NoHttpResponseException 的原因,这样能看得更明白一些

费了这么大的功夫,我们终于知道了产生 NoHttpResponseException 的原因,那该怎么解决呢,有两种策略

  1. 重试,收到异常后,重试一两次,由于重试后客户端会用有效的连接去请求,所以可以避免这种情况,不过一次要注意重试次数,避免引起雪崩!
  2. 设置一个定时线程,定时清理上述的闲置连接,可以将这个定时时间设置为 keep alive timeout 时间的一半以保证超时前回收。

evictExpiredConnections 就是用的上述第二种策略,来看下官方用法使用说明

Makes this instance of HttpClient proactively evict idle connections from theconnection pool using a background thread.

调用这个方法只会产生一个定时线程,那为啥应用中线程会一直增加呢,因为我们对每一个请求都创建了一个 HttpClient! 这样由于创建每一个 HttpClient 实例j时都会调用 evictExpiredConnections ,导致有多少请求就会创建多少个定时线程!

还有一个问题,为啥线上四台机器几乎同一时间点全挂呢?因为由于负载均衡,这四台机器的权重是一样的,硬件配置也一样,收到的请求其实也可以认为是差不多的,这样这四台机器由于创建 HttpClient 而生成的后台线程也在同一时间达到最高点,然后同时 OOM。

解决问题

所以针对以上提到的问题,我们首先把 HttpClient 改成了单例,这样保证服务启动后只会有一个定时清理线程,另外我们也让运维针对应用的线程数做了监控,如果超过某个阈值直接告警,这样能在应用 OOM 前及时发现处理。

画外音:再次强调,监控相当重要,能把问题扼杀在摇篮里!

总结

本文通过线上四台机器同时 OOM 的现象,来详细剖析定位了产生问题的原因,可以看到我们在应用某个库时首先要对这个库要有充分的了解(上述 HttpClient 的创建不用单例显然是个问题),其次必要的网络知识还是需要的,所以要成为一个合格的程序员,不光对语言本身有所了解,还要对网络,数据库等也要有所涉猎,这些对排查问题以及性能调优等会有非常大的帮助,再次,完善的监控非常重要,通过触发某个阈值提前告警,可以将问题扼杀在摇篮里!

震惊!线四台机器同一时间全部 OOM,到底发生了什么? 案发现场 昨天晚上突然短信收到 APM (即 Application Performance Management 的简称),我们内部自己搭建了这样一套系统来对应用的性能、可靠性进行线上的监控和预警的一种机制)大量告警画外音: 监控是一种非常重要的发现问题的手段,没有的话一定要及时建立哦 紧接着运维打来电话告知线上部署的四台机器全部 OOM (out of memory, 内存不足),服务全部... 阅读详情

相关推荐

记一次OOM Killer导致的生产事故

程序突然消失的问题排查:OOM Killer

li644872790的博客 604

Bug 引发的 18 次重大事故

无论工程师做了多少枯燥的测试工作,无论他们熬了多少不眠之夜在编程,但最终他们得到的是:会导致软件彻底出问题的 bug。你知道吗,由于软件故障(bug),美国经济每年在浪费生产力、返工和实际毁坏上损失了数十亿美元,软件故障背后的常见原因有: · 错误的结构定义和底层设计; · 脱离大量可靠数据和分析的强制性计划或里程碑日期; · 没有考虑和调...

gaohuanjie的专栏 9923

生产事故 java_记一次生产事故:30万单就这样没了!

背景你好,我是彤哥。昨天晚上下班回家,在地铁上,老大突然打来电话,B系统生产环境响应缓慢,影响了A系统的使用,几万小哥收不了单,大概有30万单卡住了,你去帮忙定位一下。我8点半左右到家,立马上线入会。重启我入会的时候,已经有同事在帮忙定位了,俗话说的好,重启能解决80%的问题,如果重启解决不了,那肯定是重启的次数还不够,呸,不对,重启解决不了,就真的要去定位了。事实证明,重启后走一波压测依然没什么...

weixin_42313675的博客 398

生产事故 java_2020 年的第一天,程序员鸭血粉丝又碰上生产事故

hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」),一位喜欢吃鸭血粉丝的程序员!2019 年,阿粉写了很多 bug,这不前一段时间OOM差点就把服务器搞挂。跨年的时刻,阿粉默默立下一个 flag,2020 年再见 bug。 可是没想到还没过几个小时,刚立的 falg 就倒下了,阿粉太难了????。。。。事件回顾新年第一天,运营反馈商户收到对账文件有问题。刚接到电话时,阿粉是一万个不...

weixin_39755218的博客 212

生产事故线四台机器同一时间全部 OOM(转)

案发现场 昨天晚上突然短信收到 APM (即 Application Performance Management 的简称),我们内部自己搭建了这样一套系统来对应用的性能、可靠性进行线上的监控和预警的一种机制)大量告警 画外音: 监控是一种非常重要的发现问题的手段,没有的话一定要及时建立哦 紧接着运维打来电话告知线上部署的四台机器全部 OOM (out of memory, 内存不足...

lixinkuan的博客 604

研发线事故风险解读之技术漏洞

本文基于上篇《线事故案例集》一文,继续深入剖析线事故的主要原因,旨在为读者提供有价值的见解与思考。

架构师日记 1380

生产事故-记一次特殊的OOM排查

系统内存不足以支撑当前业务场景所需要的内存,过小的机器内存或者不合理的JVM内存参数。如果排除所有合理选项,最不合理那个会不会就是答案呢?遂开始检查机器的内存,根据运维的说法,机器内存为16GB,top命令查看java进程占用内存约为7.8GB,看起来似乎没毛病。但是随后另一个同事注意到了一个事情,最后一次系统升级的时候,改动过应用启停脚本,对比旧版本的脚本,发现差异部分就是内存参数:看到这里,屏幕前的一众同事都无语啊……

MM29Fq8WX的博客 240

一次生产事故引发的MySQL参数调优

某天上午11点多,某个项目的项目经理突然打电话说项目环境突然访问提示“数据库或表结构错误”,赶紧看下。于是,迅速连接VPN,登录堡垒机,访问数据库所在的虚拟机,连不了,一直卡死。打电话联系客户后台管理员,客户后台管理员刚好请假了不在电脑旁。

杜莱的博客 725

JVM实战—13.OOM生产案例

接着就可以向Jetty监听的9090端口发送请求,Jetty会把请求转交给我们用的Spring MVC之类的框架,而Spring MVC之类的框架再去调用我们写好的Controller之类的代码。接着开始分析生产现场的内存快照,在分析MAT时发现了一个问题:因为有大量的XXClass.process()方法递归执行,每个XXClass.process()方法中都创建了大量的char数组,最后因为XXClass.process()方法又多次递归调用,也就导致了大量的char[]数组耗尽了内存。

weixin_55010563的博客 1119

面试官问:如果线上遇到OOM,你该如何排查?

今天分享一篇最近看到的文章,主题是由于 JVM 参数设置错误而导致的生产事故,对于 JVM 新手小白来说有很好的警示作用。事故背景2023年3月10日14时19分,C公司开发人员向A公司开发人员反映某开放接口从2023年3月10日14时许开始无法访问和使用。该系统为某基础数据接口服务,基于 HTTP 协议进行通信。按照惯例,首先排查网络是否异常,经运维人员检查,证明网络连通性没有问题。A公司开发组...

Java精选 509

-Xms8g -Xmx8g -Xmn3g 生产事故-记一次特殊的OOM排查

用*MAT(Memory Analyzer Tool)*工具打开转储文件,原以为会发现某个类型对象占用大量的内存,结果出乎意料,Histogram(直方图)中显示活跃对象居然只有100多M!你会发现它又可以了。这是一个为了验证而打造的极端例子,实际上生产的应用情况会比这个复杂得多,但这并不妨碍我们理解它的意图。遂开始检查机器的内存,根据运维的说法,机器内存为16GB,然后在 GC 日志里面会看到,触发 GC 的前后,Old 区几乎都没有空间,仅有的一点点还是。文件,在日志中搜索,找到了若干处内存溢出错误。

IT枫斗者的博客 732

线上服务突然 OOM 重启

线上服务突然 OOM 重启

weixin_45817985的博客 161

紧急规避!Docker默认shm-size带来的生产事故风险(附解决方案)

解决Docker容器因默认shm-size过小导致的生产故障,详解调整Docker容器的共享内存大小方法,适用于高并发、AI计算等场景,避免程序崩溃。配置简单且兼容性强,运维优化必备技巧,值得收藏。

DeepLens的博客 1012

ThreadLocal内存泄漏原理与线OOM排查实战

ThreadLocal作为Java线程封闭技术的核心实现,通过ThreadLocalMap为每个线程维护独立的变量副本。其底层采用弱引用关联Key的设计,在未正确调用remove()且线程长期存活时,会导致Value对象持续累积引发内存泄漏。这类问题在Web应用线程池场景尤为常见,表现为Old Gen内存线性增长、YGC效率下降等典型症状。通过jmap分析对象分布、MAT追踪GC Roots等技术手段,可以快速定位泄漏点。最佳实践包括声明static变量、finally块清理、使用Transmittable

weixin_34274029的博客 336

vLLM镜像版本管理建议:避免线事故的关键

本文深入探讨vLLM镜像版本管理的重要性,指出使用不稳定的版本可能导致OOM、延迟增加等线事故。强调必须避免使用latest标签,固化依赖,实施灰度发布与监控,确保生产环境稳定。结合PagedAttention和连续批处理技术优势,提出科学的版本控制策略。

weixin_28949937的博客 312

嗯?线上又OOM,我太难了...

「关注“石杉的架构笔记”,大厂架构经验倾囊相授」儒猿技术团队最新出品《大型企业级分布式订单系统项目实战》(点击查看详情)文章来源:石杉的架构笔记原创文章今天给大家分享一个我们之前基于...

石杉的架构笔记 561
上一篇: 服务注册发现技术对比
下一篇: 深入理解Linux IO复用之epoll
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值