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

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

现象

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

一天偶尔出现几次,持续时间一般几分钟不等。 当这种情况出现时,我们检查错误日志,发现有下面两几种 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. Zookeeper的安装配置及基本开发

    一.简介 Zookeeper 是分布式服务框架,主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务.状态同步服务.集群管理.分布式应用配置项的管理等等. ZooKeeper的目标就 ...

  2. [OpenCV]拓展图像边界

    图像处理中经常遇到使用当前像素邻的像素来计算当前像素位置的某些属性值,这样就会导致边界像素处越界访问,一般有两种方法解决这种问题:只对不越界的像素进行处理:对图像边界进行拓展,本文主要介绍如何使用Op ...

  3. 远程拷贝、查看端口、vim常见快捷键、查找替换命令、grep命令、查看存储空间的命令、chkconfig命令、系统自动启动级别、主机名配置、IP地址配置、域名映射、防火墙设置

    2.1.远程拷贝 (将/export/servers/hadoop上的文件拷贝到bigdate@192.168.1.1:/export/servers/ ) scp –r /export/server ...

  4. 阿里云手动安装特定版本的nginx

    想添加nginx的缓存功能, 结果1.4.6还不支持. apt-get remove nginx 374 sudo apt-key add nginx_signing.key 375 deb http ...

  5. [sed]命令笔记

    sed是linux下经常用到的工具,英文全名为stream editor. sed 在windows上的实现可以在这里找到 http://gnuwin32.sourceforge.net/packag ...

  6. Request和Response的格式

    Request和Response的格式 Request格式: HTTP请求行  (请求)头  空行  可选的消息体 注:请求行和标题必须以<CR><LF> 作为结尾(也就是,回 ...

  7. JAVA面向对象-----super关键字

    JAVA面向对象-–super关键字 1:定义Father(父类)类 1:成员变量int x=1; 2:构造方法无参的和有参的,有输出语句 2:定义Son类extends Father类 1:成员变量 ...

  8. Swift快速给Cocoa库内置类添加便捷初始化器

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) Cocoa中的NSShadow类默认没有我们需要的实例方法,为 ...

  9. 【IOS 开发】Objective - C 面向对象高级特性 - 包装类 | 类处理 | 类别 | 扩展 | 协议 | 委托 | 异常处理 | 反射

    一. Objective-C 对象简单处理 1. 包装类 (1) 包装类简介 NSValue 和 NSNumber : -- 通用包装类 NSValue : NSValue 包装单个 short, i ...

  10. GDAL不支持创建PCIDSK的面状矢量格式

    最近在使用GDAL创建PCIDSK格式的矢量数据,发现创建点和线的矢量数据都没问题,创建面状的只有属性表没有图形.在GDAL官网说明也写的是支持的,地址为:http://www.gdal.org/fr ...