震惊!线上四台机器同一时间全部 OOM,到底发生了什么?
案发现场
昨天晚上突然短信收到 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** 的原因,那该怎么解决呢,有两种策略
- 重试,收到异常后,重试一两次,由于重试后客户端会用有效的连接去请求,所以可以避免这种情况,不过一次要注意重试次数,避免引起雪崩!
- 设置一个定时线程,定时清理上述的闲置连接,可以将这个定时时间设置为 keep alive timeout 时间的一半以保证超时前回收。
evictExpiredConnections 就是用的上述第二种策略,来看下官方用法使用说明
Makes this instance of HttpClient proactively evict idle connections from the
connection pool using a background thread.
调用这个方法只会产生一个定时线程,那为啥应用中线程会一直增加呢,因为我们对每一个请求都创建了一个 HttpClient! 这样由于每一个 HttpClient 实例都会调用 evictExpiredConnections ,导致有多少请求都会创建多少个 定时线程!
还有一个问题,为啥线上四台机器几乎同一时间点全挂呢?
因为由于负载均衡,这四台机器的权重是一样的,硬件配置也一样,收到的请求其实也可以认为是差不多的,这样这四台机器由于创建 HttpClient 而生成的后台线程也在同一时间达到最高点,然后同时 OOM。
解决问题
所以针对以上提到的问题,我们首先把 HttpClient 改成了单例,这样保证服务启动后只会有一个定时清理线程,另外我们也让运维针对应用的线程数做了监控,如果超过某个阈值直接告警,这样能在应用 OOM 前及时发现处理。
画外音:再次强调,监控相当重要,能把问题扼杀在摇篮里!
总结
本文通过线上四台机器同时 OOM 的现象,来详细剖析产定位了产生问题的原因,可以看到我们在应用某个库时首先要对这个库要有充分的了了解(上述 HttpClient 的创建不用单例显然是个问题),其次必要的网络知识还是需要的,所以要成为一个合格的程序员,不关对语言本身有所了解,还要对网络,数据库等也要有所涉猎,这些对排查问题以及性能调优等会有非常大的帮助,再次,完善的监控非常重要,通过触发某个阈值提前告警,可以将问题扼杀在摇篮里!
收有收获,点赞再走哦
欢迎关注公众「码海」,更多干货等你来看!
震惊!线上四台机器同一时间全部 OOM,到底发生了什么?的更多相关文章
- centos 7.2 同步北京时间 ,多台机器同步时间
linux 系统没有北京时间,同步的是上海时间 linux 系统有两个时钟:一个是硬件时钟,即BIOS时间:另一个是系统时钟,是linux系统Kernel(内核)时间. 系统开启时,系统会读取硬件时间 ...
- 线上mongodb 数据库用户到期时间修改的操作记录
登陆版权数据库,显示"此用户已到期",数据库使用的是mongodb,顾 需要将此用户的到期时间延长. 解决过程: 1)到网站对应tomcat配置里找出等里mongodb的信息(mo ...
- 如何同步两台Linux机器的时间?
除了用NTP服务器之外,下面的命令可以做到让几台机器的时间都跟一台机器同步. date --set="$(ssh root@10.245.110.101 date)" 参考资料 = ...
- shell远程操作另外一台机器上数据
shell远程操作另外一台机器上的数据,有两种方式: 1 .配置免密登陆,2.使用sshpass 当前存在两台虚拟机,ip地址分别为:192.168.3.32 192.168.3.33 一.免密登陆操 ...
- 记一次线上coredump事故
1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发生在业务低峰期,而且该模块提供的功能也不是核心流程功能,所以对线上业务影响比较小.发生coredump后,运维收到 ...
- 一脸懵逼学习Hadoop分布式集群HA模式部署(七台机器跑集群)
1)集群规划:主机名 IP 安装的软件 运行的进程master 192.168.199.130 jdk.hadoop ...
- 线上日志集中化可视化管理:ELK
本文来自网易云社区 作者:王贝 为什么推荐ELK: 当线上服务器出了问题,我们要做的最重要的事情是什么?当需要实时监控跟踪服务器的健康情况,我们又要拿什么去分析?大家一定会说,去看日志,去分析日志.是 ...
- 线上MYSQL同步报错故障处理总结(转)
前言 在发生故障切换后,经常遇到的问题就是同步报错,数据库很小的时候,dump完再导入很简单就处理好了,但线上的数据库都150G-200G,如果用单纯的这种方法,成本太高,故经过一段时间的摸索,总结了 ...
- 线上Java程序占用 CPU 过高,请说一下排查方法?
我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...
随机推荐
- NOIP模拟测试6
看题目就知道这是一个悲伤的故事... 但还有更悲伤的 考崩了,难以描述. T1把数据范围看成2^12,我TM也是够了... T2思路接近正解,但不知道想了个神魔东西跑了N遍dijstra T3最狗了, ...
- 二、安装JDK - Java软件的安装
jdk是 Java 语言的软件开发工具包,主要用于移动设备.嵌入式设备上的java应用程序. 1.安装包的下载:http://pan.baidu.com/s/1mgh58ve (该安装包是绿色的,解压 ...
- 谷歌Chrome浏览器无法安装插件的解决方法(本文干货!)
这个问题困扰了我很久,作为小白学习可能会用到谷歌插件,奈何谷歌也太变态,国内的环境无法正常登录谷歌账户.无法访问应用商店,而Chrome主版本号大于66的只能从Chrome应用商店下载并安装插件,各种 ...
- centos7上以RPM方式安装MySQL5.6
1. 下载MySQL http://ftp.ntu.edu.tw/MySQL/Downloads/MySQL-5.6/ MySQL-5.6.36-1.el7.src.rpm MySQL-5.6.36- ...
- sublime3中运行python文件
sublime3中运行python文件 tools->build system->new build stystem 粘贴下面代码{"cmd":["pytho ...
- Java基础语法07-面向对象-多态
抽象类 抽象方法 : 没有方法体的方法. 抽象类:被abstract所修饰的类. 抽象类的语法格式: [权限修饰符] abstract class 类名{ }[权限修饰符] abstract clas ...
- Typescript I: 遍历Array的方法:for, forEach, every等
Typescript的官方文档 Iterators and Geneators (https://www.typescriptlang.org/docs/handbook/iterators-and- ...
- C#: 统计method的执行时间
对于性能分析来说,无非是内存占用,CPU使用和执行时间. 那么,对于执行时间(elapsed times)的测量,需要强调的是,尽量不要使用DateTime类来,而是应该使用Stopwatch 类.M ...
- C++中对封装的语法支持——静态成员
静态成员(变量与函数) 1.静态成员变量的语法.访问.特点(共享.类内声明类外初始化) 静态成员变量在class中只做声明,并没有初始化所以不会分配内存. (1) 非静态成员变量必须通过对象来访问. ...
- C++中对C的扩展学习新增语法——const
Const Const在C语言和C++语言中连接属性不一样,C语言默认是外部连接,如果需要内部连接,需要显示写上static.而在C++中默认是内部连接,如果希望其编程外部变量,需要显示写上exte ...