「推断的前提是以事实为依据。」

这两天碰到一个线上系统的偶尔出现突然堆内存暴涨,这倒不是个什么疑难杂症, 只是过程中有些思路觉得可以借鉴参考,故总结下并写下来。

现象

内存情况可以看看下面这张监控图。

一天偶尔出现几次,持续时间一般几分钟不等。 当这种情况出现时,我们检查错误日志,发现有下面两几种 OOM 错误。

  • java.lang.OutOfMemoryError: GC overhead limit exceeded
  • java.lang.OutOfMemoryError: Java heap space

与之相伴的还有个错误日志,是访问 redis 抛出的异常

  • JedisConnectionException: java.net.SocketException: Broken pipe

我们一共观察到的现象大概就是这么多了,观察了两天感觉现象发生没什么规律性,持续时间也不长,一会儿应用 又会自动就恢复正常了。

诊断

通过上面的现象,负责系统开发和维护的童鞋认为可能是网络不稳定, java.net.SocketException: Broken pipe 这个异常看起来确实是连接 redis 的长连接中断了, 而出现这个问题的应用,正好是我们新部署在一个新的 IDC,它需要访问在老 IDC 部署的 redis, 而在老 IDC 部署的应用则没出现过此类现象。

虽然两个 IDC 之间通过高宽带光纤连接作成了局域网,但依然比同一 IDC 内相比要慢上一些,再加上这个伴生 的应用抛出的网络异常,让人容易判断是网络环境的稳定性可能有区别导致应用行为的差别。 只是连接 redis 的长连接中断和应用抛出 OOM 有什么关联?我咋一想没觉得有必然联系。 而且负责网络监控的同事也确定两个 IDC 之间在发生应用异常时网络很稳定,完全没有丢包现象,带宽也足够。 因此将其原因推断于网络不稳定,就显得让人不太能理解,难以信服。

而且在 OOM 中能自己恢复的应用就不是内存泄露,应该属于内存溢出。 大概可能就是应用申请的内存短时间内超出了 JVM 堆的容量,导致抛出 OOM,从上面抛出的两种类型的 OOM 看确实像这种情况,特别是提示 GC overhead limit exceeded 这个说明就更指向了代码可能有问题。 只是如何找到哪段代码有问题,这个只好先通过在 OOM 时 dump 内存来分析了,在应用启动时加入下面启动参数 来捕捉 OOM 现场。

  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=mem.dump

弄到了内存 dump 文件后,用 jhat 或 MAT 分析,顺利找到了某个线程在当时申请了 1.6G 内存,再顺着 线程栈找到了调用方法,一看源码立刻明白了,代码所在方法提供了对外的接口服务,方法参数来自外部输入,没有 对输入参数作安全性判断,而是直接根据输入参数确定边界创建了一个超级大的数组(2000多万个整数),导致立刻触发 了 OOM 并持续 FullGC 一段时间后被直接回收了,所以内存曲线才会像上图中那样。

再想想

现象中还有个连接 redis 的网络异常,这又是怎么回事? 再回到代码去看,原来那个拼出来的 2000 多万个整数元素数组,是作为访问 redis 的命令参数(hmget)。 到这里,瞬间明白了是吧,这么长的参数做过服务端网络编程的都明白,协议解析时超过一个合理长度估计就会被拒绝 被认为是恶意的客户端,而导致服务端拒绝该客户端,拒绝的行为一般都是关闭连接。

再去扒了下 redis 的文档,确实找到了这样的说明:

Query buffer hard limit Every client is also subject to a query buffer limit. This is a non-configurable hard limit that will close the connection when the client query buffer (that is the buffer we use to accumulate commands from the client) reaches 1 GB, and is actually only an extreme limit to avoid a server crash in case of client or server software bugs.

上面就是说 redis 最大能接受的命令长度是写死在代码里地,就是 1 GB,超过这个自然被拒绝了。 更多关于细节参看 redis 官方文档

总结

我觉得从这个案例中收获了两点感悟:

  1. 现象并不那么可靠,不能头痛医头脚痛医脚。
  2. 先从怀疑自己的代码开始。

第一点,应该是个常识了,医生诊断的例子充分说明了这点。 第二点,为什么要先从怀疑自己代码开始呢,简单来说就是应用的业务代码通常是测试和验证最不充分的代码。 业务应用依赖的环境不论是硬件(主机、网络、交换机)的还是软件的(操作系统、JVM、三方库)这些通常都比业务代码 经过更多地测试和广泛地应用验证,所以要先从怀疑自己开始,除非有非常明确地证据指向其他方面, 个人经验大部分时候这都是找到问题的最短路径。

----------------------------

下面是我自己开的一个微信公众号 [瞬息之间],除了写技术的文章、还有产品的、行业和人生的思考,希望能和更多走在这条路上同行者交流,有兴趣可关注一下,谢谢。

一个 redis 异常访问引发 oom 的案例分析的更多相关文章

  1. MySQL服务器发生OOM的案例分析

    [问题] 有一台MySQL5.6.21的服务器发生OOM,分析下来与多种因素有关 [分析过程] 1.服务器物理内存相对热点数据文件偏小,62G物理内存+8G的SWAP,数据文件大小约550G 触发OO ...

  2. MySQL 5.7 GTID OOM bug案例分析 --大量压测后主从不同步

    转载自:http://www.sohu.com/a/231766385_487483 MySQL 5.7是十年内最为经典的版本,这个观点区区已经表示过很多次.然而,经典也是由不断地迭代所打造的传奇.5 ...

  3. keepalived主备节点都配置vip,vip切换异常案例分析

    原文地址:http://blog.51cto.com/13599730/2161622 参考地址:https://blog.csdn.net/qq_14940627/article/details/7 ...

  4. 分布式架构-Redis 从入门到精通 完整案例 附源码

    导读 篇幅较长,干货十足,阅读需要花点时间,全部手打出来的字,难免出现错别字,敬请谅解.珍惜原创,转载请注明出处,谢谢~! NoSql介绍与Redis介绍 什么是Redis? Redis是用C语言开发 ...

  5. C++异常处理解析: 异常的引发(throw), 捕获(try catch)、异常安全

    前言: C++的异常处理机制是用于将运行时错误检测和错误处理功能分离的一 种机制(符合高内聚低耦合的软件工程设计要求),  这里主要总结一下C++异常处理的基础知识, 包括基本的如何引发异常(使用th ...

  6. 在Windows上弄一个redis的docker容器

    [本文出自天外归云的博客园] Docker核心概念简介 镜像是一个面向docker引擎的只读模板,包含了文件系统. 镜像是创建容器的基础,容器类似于一个沙箱,用来运行和隔离应用. 容器是从镜像创建的应 ...

  7. Redis偶发连接失败案例分析

    [作者] 张延俊:携程技术保障中心资深DBA,对数据库架构和疑难问题分析排查有浓厚的兴趣. 寿向晨:携程技术保障中心高级DBA,主要负责携程Redis及DB的运维工作,在自动化运维,流程化及监控排障等 ...

  8. 学习T-io框架,从写一个Redis客户端开始

    前言   了解T-io框架有些日子了,并且还将它应用于实战,例如 tio-websocket-server,tio-http-server等.但是由于上述两个server已经封装好,直接应用就可以.所 ...

  9. 搭建一个redis高可用系统

    一.单个实例 当系统中只有一台redis运行时,一旦该redis挂了,会导致整个系统无法运行. 单个实例 二.备份 由于单台redis出现单点故障,就会导致整个系统不可用,所以想到的办法自然就是备份( ...

随机推荐

  1. linux下安装apache(httpd-2.4.3版本)各种坑

    博主的linux是ubuntu 14.04.3. 在安装apache最新版httpd-2.4.3的时候遇到各种坑. 先提供安装apache httpd-2.4.3所需要的包,博主已经整理好,下载地址: ...

  2. HA机制下的Hadoop配置

    [版权申明:本文系作者原创,转载请注明出处] 文章出处:http://www.cnblogs.com/sdksdk0/p/5585355.html 作者: 朱培    ID:sdksdk0 ----- ...

  3. Python 2.7的字典实现简化版(C语言)

    这是一个能自动调整大小的哈希字典,外部接口实现了下列功能. 1.字典级别: 创建字典 dict_new 归零字典 dict_clear 2.键值级别: 查找 dict_search 强制查找 dict ...

  4. 使用反射创建Bean、Spring中是如何根据类名配置创建Bean实例、Java提供了Class类获取类别的字段和方法,包括构造方法

    Java提供了Class类,可以通过编程方式获取类别的字段和方法,包括构造方法    获取Class类实例的方法:   类名.class   实例名.getClass()   Class.forNam ...

  5. Effective C++ ——设计与声明

    条款18:让接口更容易的被使用,不易误用 接口设计主要是给应用接口的人使用的,他们可能不是接口的设计者,这样作为接口的设计者就要对接口的定义更加易懂,让使用者不宜发生误用,例如对于一个时间类: cla ...

  6. RecyclerView下拉刷新上拉加载(一)

    listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...

  7. 文件操作:fseek函数和ftell函数

    1.fseek函数: int fseek(FILE * _File, long _Offset, int _Origin); 函数设置文件指针stream的位置.如果执行成功,stream将指向以fr ...

  8. 06 Activity显示跳转

    <span style="font-size:18px;">package com.fmy.day8_29task; import com.fmy.day8_29tas ...

  9. Qt中事件分发源代码剖析

    Qt中事件分发源代码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,并且进行分发,这些都是在exec中进行的. 下面举例说明: 1)首先看看下面一段示例代码: i ...

  10. Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源码实现

    如果Spark的部署方式选择Standalone,一个采用Master/Slaves的典型架构,那么Master是有SPOF(单点故障,Single Point of Failure).Spark可以 ...