一个 redis 异常访问引发 oom 的案例分析
「推断的前提是以事实为依据。」
这两天碰到一个线上系统的偶尔出现突然堆内存暴涨,这倒不是个什么疑难杂症, 只是过程中有些思路觉得可以借鉴参考,故总结下并写下来。
现象
内存情况可以看看下面这张监控图。

一天偶尔出现几次,持续时间一般几分钟不等。 当这种情况出现时,我们检查错误日志,发现有下面两几种 OOM 错误。
java.lang.OutOfMemoryError: GC overhead limit exceededjava.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 官方文档
总结
我觉得从这个案例中收获了两点感悟:
- 现象并不那么可靠,不能头痛医头脚痛医脚。
- 先从怀疑自己的代码开始。
第一点,应该是个常识了,医生诊断的例子充分说明了这点。 第二点,为什么要先从怀疑自己代码开始呢,简单来说就是应用的业务代码通常是测试和验证最不充分的代码。 业务应用依赖的环境不论是硬件(主机、网络、交换机)的还是软件的(操作系统、JVM、三方库)这些通常都比业务代码 经过更多地测试和广泛地应用验证,所以要先从怀疑自己开始,除非有非常明确地证据指向其他方面, 个人经验大部分时候这都是找到问题的最短路径。
----------------------------
下面是我自己开的一个微信公众号 [瞬息之间],除了写技术的文章、还有产品的、行业和人生的思考,希望能和更多走在这条路上同行者交流,有兴趣可关注一下,谢谢。
一个 redis 异常访问引发 oom 的案例分析的更多相关文章
- MySQL服务器发生OOM的案例分析
[问题] 有一台MySQL5.6.21的服务器发生OOM,分析下来与多种因素有关 [分析过程] 1.服务器物理内存相对热点数据文件偏小,62G物理内存+8G的SWAP,数据文件大小约550G 触发OO ...
- MySQL 5.7 GTID OOM bug案例分析 --大量压测后主从不同步
转载自:http://www.sohu.com/a/231766385_487483 MySQL 5.7是十年内最为经典的版本,这个观点区区已经表示过很多次.然而,经典也是由不断地迭代所打造的传奇.5 ...
- keepalived主备节点都配置vip,vip切换异常案例分析
原文地址:http://blog.51cto.com/13599730/2161622 参考地址:https://blog.csdn.net/qq_14940627/article/details/7 ...
- 分布式架构-Redis 从入门到精通 完整案例 附源码
导读 篇幅较长,干货十足,阅读需要花点时间,全部手打出来的字,难免出现错别字,敬请谅解.珍惜原创,转载请注明出处,谢谢~! NoSql介绍与Redis介绍 什么是Redis? Redis是用C语言开发 ...
- C++异常处理解析: 异常的引发(throw), 捕获(try catch)、异常安全
前言: C++的异常处理机制是用于将运行时错误检测和错误处理功能分离的一 种机制(符合高内聚低耦合的软件工程设计要求), 这里主要总结一下C++异常处理的基础知识, 包括基本的如何引发异常(使用th ...
- 在Windows上弄一个redis的docker容器
[本文出自天外归云的博客园] Docker核心概念简介 镜像是一个面向docker引擎的只读模板,包含了文件系统. 镜像是创建容器的基础,容器类似于一个沙箱,用来运行和隔离应用. 容器是从镜像创建的应 ...
- Redis偶发连接失败案例分析
[作者] 张延俊:携程技术保障中心资深DBA,对数据库架构和疑难问题分析排查有浓厚的兴趣. 寿向晨:携程技术保障中心高级DBA,主要负责携程Redis及DB的运维工作,在自动化运维,流程化及监控排障等 ...
- 学习T-io框架,从写一个Redis客户端开始
前言 了解T-io框架有些日子了,并且还将它应用于实战,例如 tio-websocket-server,tio-http-server等.但是由于上述两个server已经封装好,直接应用就可以.所 ...
- 搭建一个redis高可用系统
一.单个实例 当系统中只有一台redis运行时,一旦该redis挂了,会导致整个系统无法运行. 单个实例 二.备份 由于单台redis出现单点故障,就会导致整个系统不可用,所以想到的办法自然就是备份( ...
随机推荐
- JAVA处理Blob大对象
Blob对象是SQL Blob的Java语言映射.SQL Blob是一个内置类型,它可以将一个二进制大对象保存在数据库中.接口ResultSet.CallableStatement和PreparedS ...
- valgrind检测内存泄漏
Valgrind 使用 用法:valgrind [options] prog-and-args [options]: 常用选项,适用于所有Valgrind工具 -tool=<name>最常 ...
- Objective-C方法与函数的区别
Objective-C方法与函数的区别 方法是唯对象所有 函数是不依赖于对象存在的 方法 函数 - (void)test; void test(); 方法是以减号 - 开头 - 类型要用()括起来 - ...
- Swift3中dispatch_once废弃的解决办法
在Swift中如果想搞类的单例模式,那么在初始化的时候一般会使用just one time执行的方式,我们使用dispatch_once_t配合调用dispatch_once方法,一般的代码如下: s ...
- Android Multimedia框架总结(十九)Camera2框架C/S模型之CameraService启动及与Client连接过程
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53150322 Agenda: 一 ...
- Java异常处理-----运行时异常(RuntimeException)
RuntimeException RunntimeException的子类: ClassCastException 多态中,可以使用Instanceof 判断,进行规避 ArithmeticExcep ...
- Apache shiro集群实现 (八) web集群时session同步的3种方法
Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...
- Redis集群教程(Redis cluster tutorial)
本博文翻译自Redis官网:http://redis.io/topics/cluster-tutorial 本文档以温和的方式介绍Redis集群,不使用复杂的方式来理解分布式系统的概念. ...
- Servlet - Listener、Filter、Decorator
Servlet 标签 : Java与Web Listener-监听器 Listener为在Java Web中进行事件驱动编程提供了一整套事件类和监听器接口.Listener监听的事件源分为Servle ...
- activiti节点跳转
分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自分享牛http://blog.csdn.net/qq_30739519) activiti使用的时候,通常需要跟业务紧密的结合在一起,有些业 ...