一个 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出现单点故障,就会导致整个系统不可用,所以想到的办法自然就是备份( ...
随机推荐
- 使用eclipse开发工具与hibernate开发者为开源一起做贡献
本文作者:苏生米沿 本文地址:http://blog.csdn.net/sushengmiyan/article/details/50525363 hibernate使用的是gradle自动构建工具, ...
- Linux SWAP 交换分区配置说明
一.SWAP 说明1.1 SWAP 概述 当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用.那些被释放的空间可能来自一些很长时间没有什么操作的 ...
- Objective-C语法概述
Objective-C语法概述 简称OC 面向对象的C语言 完全兼容C语言 可以在OC里面混入C/C++代码 可以开发IOS和Mac OS X平台应用 语法预览 关键字 基本上都是以@开头(为了与C语 ...
- Web自动化框架LazyUI使用手册(4)--控件抓取工具Elements Extractor详解(批量抓取)
概述 前面的一篇博文详细介绍了单个控件抓取的设计思路&逻辑以及使用方法,本文将详述批量控件抓取功能. 批量抓取:打开一个web页面,遍历页面上所有能被抓取的元素,获得每个元素的iframe.和 ...
- 详解EBS接口开发之采购订单导入
采购订单常用标准表简介 1.1 常用标准表 如下表中列出了与采购订单导入相关的表和说明: 表名 说明 其他信息 po.po_headers_all 采购订单头 采购订单号,采购类型,供应商,地点, ...
- AsnycTask的内部的实现机制
AsnycTask的内部的实现机制 写在前面 我们为什么要用AsnycTask. 在Android程序开始运行的时候会单独启动一个进程,默认情况下所有 这个程序操作都在这个进程中进行.一个Androi ...
- Android Multimedia框架总结(三)MediaPlayer中创建到setDataSource过程
转载请把头部出处链接和尾部二维码一起转载,本文出自:http://blog.csdn.net/hejjunlin/article/details/52392430 前言:前一篇的mediaPlayer ...
- 关于LT分发系统的设计构想
git地址 https://github.com/cxyxd/LtDistribution 背景 对tomcat做集群,在多机多tomcat的情况下,如果要更新代码,只能手动的将代码复制,粘贴,然后下 ...
- Android View框架总结(六)View布局流程之Draw过程
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52236145 View的Draw时序图 ViewRootImpl.p ...
- 14 fragment 创建
静态展示 注意 静态的开始进入界面的生命周期和动态的不同 详情:14 fragment注意点 步骤一:创建一个类继承 Fragment 代码类型一: package com.fmy.demo1; im ...