线上问题排查实例分析|关于 Redis 内存泄漏

news2024/11/14 20:38:22

Redis 作为高性能的 key-value 内存型数据库,普遍使用在对性能要求较高的系统中,同时也是滴滴内部的内存使用大户。本文从 KV 团队对线上 Redis 内存泄漏定位的时间线维度,简要介绍 Linux 上内存泄漏的问题定位思路和工具。

16:30 问题暴露

业务反馈缩容后内存使用率90%告警,和预期不符合,key 只有1万个,使用大 key 诊断,没有超过512字节以上的大 key。

16:40 确认内存泄漏

发现该系统中有部分实例内存明显偏高达到300~800MB,正常实例只有10MB左右,版本号为4ce35dea,在9月份时已经有发现49bdcd0b这个较老版本有内存泄漏情况发生,现象看起来一样,说明内存泄漏问题一直存在,未被修复,于是开始排查该问题。

17:30 开始排查社区版本

排查问题先易后难,先排除是不是社区的版本Bug问题:

  • 不需要从最新修复一直倒叙确认到3系列的 commit 提交,因为如果是严重的内存泄漏,3系列的旧版本也一定会有 backport 修复记录。

查看3.2.8的commit记录,只有一次内存泄漏相关提交:Memory leak in clusterRedirectBlockedClientIfNeeded.

本次提交只修复了在 cluster 出现 key 重定向错误时对 block client 处理时对一个指针的泄漏,不可能出现如此大的泄漏量。3.2.8的社区版已上线数年,但在社区内未搜索到相关内存泄漏问题,因此推测是我们的某些定制功能开发引入的 Bug。

18:10 整理监控和日志

整理当前已知监控和日志信息,分析问题的表面原因和发生时间

1、监控信息

odin 监控只能看到最近两个月的内存使用曲线,从监控上可以得到三点信息:

  • 两个月前已经发生内存泄漏

  • 内存泄漏不是持续发生的,是由于某次事件触发的

  • 内存泄漏量大,主实例使用内存800MB,从实例使用内存10MB

21c89a9b5aa7a9dda8229161a4fcaea6.png

2、日志信息

排查发生内存泄漏的容器日志:       

36a6a8d5612614e074cccaef63fa96f0.png

07161cf1671c59ba5012d8eb4adafdc7.png

Redis 在10月11日被创建后,只有在20日出现有大量日志,之后无日志,日志有以下内容:

  • Redis 横向扩容 slot 迁移

  • 主从切换

  • AOF 重写

  • 搜索该系统的历史短信告警,在10月11日11:33分出现三次内存使用率达到100%的告警,因此可以推测出现 key 淘汰

Manager平台操作信息:

71e708e93d03f80747d14a6a1d8d80fc.png

  • 垂直扩容

  • 横向扩容

  • Redis 重启

综合 Redis 的日志和平台日志信息,虽然未能直接发现问题原因,可以确定内存泄漏发生在10月20日11:30左右,由以下单个事件或者混合触发的:

  • 主从切换

  • key 迁移

  • key 驱除

18:00 打印内存 dump 信息

在实例上使用 GDB  把泄漏实例的所有内存 dump 出来,初步发现内存上有很多 key(647w个),不属于本节点,info 里数据库只有1.6W个 key,  怀疑是slot 迁移有问题。

803061b9a2d6c26ab1d6f227d73e86e1.png

18:30 第一次 diff 代码

由于3.2.8自研版本有两个重大修改:

  1. slot 的所属 key 集合记录,把跳跃表改为了4.0以后的基数树结构,从社区的 unstable 分支 backport 下来的;

  2. 支持多活

由于出问题的系统没有使用多活功能,且恰巧事发时有 slot 迁移,因此重点怀疑 slot 迁移中 rax 树相关操作有内存泄漏,首先查看了相关代码,有几个疑似的地方,但都排除掉了。

20:30 尝试使用工具定位

  1. memory doctor

    Redis4 引入的内存诊断命令,3系列未实现

  2. 3.2.8版本使用 jemalloc-4.0.3作为内存分配器,尝试使用 jeprof 工具分析内存使用情况,发现 jemalloc 编译时需要提前添加--enable-prof编译选项,此路不通

  3. 使用 perf 抓取 brk 系统调用,未发现异常(实际上最近两个月也未发生泄漏)

  4. valgrind 作为最后手段,不确定是否可以复现

22:00 组内沟通进展

和组内同学沟通下午的调查情况,仍然怀疑 rax 泄漏,其次多活或者 failover 混合动作触发的 case 导致泄漏。

第二天10:00 重新整理思路

使用 hexdump 观察昨天的内存 dump 文件,发现泄漏内存为 SDS 字符串数据类型,且连续分布。

1552109436f54ecc7c44bfc325e97df7.png

每隔4、5行都会出现OO TT SS等字符,对应 SDS 类型的 sdshdr 结构体。      

5868a6662eb195e238affee34eca7fab.png

每个泄漏的 key 字符串大约在80字节左右,因此使用时 sdshdr8(为了节约内存,sds 的 header 有五种 sdshdr5,sdshdr8、sdshdr16、sdshdr32、sdshdr64,其中8指的是长度小于1<<8的字符串使用的 sdshdr)。    

a0b9ea6cd80567ed1d54daee801c945a.png

以TT那行为例,结合 SDS 字符串的 new 函数分析,key 字符串长度为84字节等于0x54,结合代码看,sh->len和sh->alloc都是0x54,第三个字节标识 type 类型,sdshdr8 的 type 值刚好是0x1,因此可以确认泄漏的是 sds 类型的 key 值,并且排除 rax 树泄漏的可能,因为内存 dump 和 rax 树的存储结构不符。附典型的 rax 存储结构:   

a5157eef46d9227f7bc3d60c11bbf402.png

14:00 根据dump的分析重新排查代码

排除了 rax 树的泄漏,同时综合 redis 使用 sds key 的情况,此时把怀疑重点放在了 write 等 dict 的释放方法上,以及 rdb 的加载时 key 的临时结构体变量。

此时 diff 代码,不再局限有变更的代码,以功能为粒度进行走读代码,但把重点放在了 failover 时的 flushdb 和 loadRDB 操作上。

17:00 排查slot迁移代码

在上一轮代码走读中,再次排除了 failover,key 淘汰的代码有内存泄漏的可能,因此重新怀疑 slot 迁移中的某些动作导致 key 字面值的内存泄漏,尤其是 slot 清空等操作。

18:30 找到根因

在 slot 迁移过程中,会遍历旧节点中的所有 key,然后把遍历得到的 key 从旧节点迁移到新节点中。

1e4911620568f73809d11ae28795ff1b.png

这个功能在3.2.8代码中没有被改动,但其调用的 getKeysInSlot 函数有了修改。getKeysInSlot 是遍历 rax 树,拿到待迁移 key 列表,对每个 key 从 rax 树中取出完整字符串,来拷贝创建 obj 类型指向 sds 字符串;这些字符串作为数组指针类型返回给了出参 keys,但在上层调用把这些字符串返回给客户端后,没有释放这些字符串,导致了内存泄漏的发生。

原生的3.2.8代码中 getKeysInSlot 函数,由于使用的是跳跃表,该跳跃表中的每个节点都是一个 key 的 obj 类型,因此只需要返回这个 key 的指针即可,无需内存拷贝动作,因此上层调用中也就不需要内存释放动作。这个根因查明,也反过来解释了很多疑问:

  • 为什么刚开始只有老版本才有内存泄漏,新版本未发现。原因是老版本的实例上线时间长,有水平扩容的需求较多,内存泄漏的实例也就较多。

  • 泄漏的内存为什么连续分布?原因是在一次 slot 迁移动作中,这些 key 遍历动作都是连续进行的。

  •  这个系统为什么泄漏比例这么高?原因是该系统中 key 占用的内存比 value值更高,key 通常80字节,而 value 大多是0、1等数值。

20:00 修复动作

相比较根因的查找,修复就简单多了,只需添加一行代码即可。   

cd96b31e0005648bc3d23e7bcecd4a1f.png

后续思考

1、代码 review 需要从功能视角去走读代码,不能只关注 diff 不同。在本次调查中,第一遍走读代码只关注 diff 点,是无法发现问题的。

2、对内存泄漏的排查,在代码设计阶段是避免此类问题的效率最优解,代码 review 阶段比测试阶段代价要小,测试阶段发现要比上线后排查容易得多,越是工程后期修复 bug 越难。具体在该函数设计中,由于内存申请和释放没有内聚性,导致内存泄漏很容易出现,而这个函数在3系列使用跳跃表时是没有问题的,因为不涉及到内存的申请释放。开发和 QA 在测试中引入工具进行功能覆盖测试,动态工具如 valgrind、sanitizers 等,线上工具如memleak、perf等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1244287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gitt开源项目的意义,公司为什么会对在gitt上有开源项目的人更大机会

Git是一种分布式版本控制系统&#xff0c;它可以帮助程序员管理代码的历史版本和协同工作。同时&#xff0c;Git也成为了开源项目的主要托管平台之一。Git的开源项目意义重大&#xff0c;因为这种开源项目托管平台可以帮助开发者将代码和项目分享给全球的开发者&#xff0c;并且…

linux网络之网络层与数据链路层

文章目录 一、网络层 1.IP协议 2.IP协议头格式 3.网段划分 4.特殊ip地址 5.IP地址的数量限制 6.私有ip和公网IP 7.路由 二、数据链路层 1.以太网 2.以太网帧格式 3.MAC地址 4.对比理解MAC地址和IP地址 5.MTU 6.ARP协议 ARP协议的工作流程 ARP数据报的格式 7.DNS 8.ICMP协议 9.N…

从六个方面对比Go和Python的差异

您是否想过 Go 与 Python 之间的主要区别是什么&#xff1f;随着对软件开发人员的需求不断增加&#xff0c;选择哪种编码语言可能会很困难。 ​ 在此&#xff0c;我们将从六个方面对比Go和Python,探讨 Go 和 Python之间的差异。我们将讨论它们的特点、优缺点&#xff0c;以便…

mongodb数据库的常用操作语句

说在前面的话 本文所有的操作示例&#xff0c;都以集合“HistoryTaskBase”为例。 一、查询 1、时间区间 查询“通知时间”介于2019-09-01到2019-10-01之间的数据。 db.getCollection(HistoryTaskBase).find({notifyTime:{$gte:ISODate(2019-09-01T00:00:00.000Z),$lte:ISOD…

Nginx:简介、安装与部署

一、Nginx简介 Nginx是一个很好的高性能Web和反向大力服务器&#xff0c;它具有很多非常优越的特性&#xff1a;在高连接并发的情况下&#xff0c;Nginx是Apahe服务器的不错的替代品&#xff1a;Nginx在美国是虚拟主机生意选择的软件平台之一。能够支持50000个并发连接数的响应…

《洛谷深入浅出基础篇》P4017最大食物链————拓扑排序

上链接&#xff1a;P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P4017 上题干&#xff1a; 题目背景 你知道食物链吗&#xff1f;Delia 生物考试的时候&#xff0c;数食物链条数的题目全都错了&#xff0c;因为她总是…

高质量短效SOCKS5代理IP是什么意思?作为技术你了解吗

小张是一位网络安全技术测试员&#xff0c;最近他接到了一个头疼的任务&#xff0c;那就是评估公司系统的安全性&#xff0c;因此他前来咨询&#xff0c;在得知SOCKS5代理IP可以帮他之后&#xff0c;他不禁产生疑问&#xff0c;这是什么原理&#xff1f;其实和小张一样的朋友不…

ELK企业级日志分析平台——kibana数据可视化

部署 新建虚拟机server5&#xff0c;部署kibana [rootelk5 ~]# rpm -ivh kibana-7.6.1-x86_64.rpm [rootelk5 ~]# cd /etc/kibana/[rootelk5 kibana]# vim kibana.ymlserver.host: "0.0.0.0"elasticsearch.hosts: ["http://192.168.56.11:9200"]i18n.local…

嵌入式开发从入门到入土

写在前面的话 嵌入式开发涉及的层面很广&#xff0c;它既有底层硬件的开发&#xff0c;又涉及上层应用的开发&#xff0c;也就是所谓的系统集成的硬件和软件。而C语言不仅具有汇编语言操作底层的优势&#xff0c;又具有高级开发语言的功能性强的特点&#xff0c;当之无愧地成为…

JavaScript框架 Angular、React、Vue.js 的全栈解决方案比较

在 Web 开发领域&#xff0c;JavaScript 提供大量技术栈可供选择。其中最典型的三套组合&#xff0c;分别是 MERN、MEAN 和 MEVN。前端框架&#xff08;React、Angular 和 Vue&#xff09;进行简化比较。 MERN 技术栈详解 MERN 技术栈包含四大具体组件&#xff1a; MongoDB&am…

保姆级 Keras 实现 YOLO v3 一

保姆级 Keras 实现 YOLO v3 一 一. YOLO v3 总览二. 特征提取网络特征提取网络代码实现 三. 特征融合特征融合代码实现 四. 网络输出模型输出代码实现 五. 网络模型代码实现六. 代码下载 如果要给 YOLO 目标检测算法一个评价的话, 就是快和准, 现在已经到了 v8, 但是我为什么还…

前端环境变量释义process.env与import.meta.env

视频教程 彻底搞懂前端环境变量使用和原理&#xff0c;超清楚_哔哩哔哩_bilibili 添加命令行参数 --modexxxxx 新建.env.xxxx文件,其中.env文件会在所有环境下生效 以VITE_开头&#xff0c;字符串无需加双引号 使用import.meta.env.VITE_xxxxx进行调用

JavaScript基础—引入方式、注释和结束符、输入和输出、变量、常量、数据类型、检测数据类型、类型转换、综合案例—用户订单信息

版本说明 当前版本号[20231123]。 版本修改说明20231123初版 目录 文章目录 版本说明目录JavaScript 基础 - 第1天介绍引入方式内部方式外部形式 注释和结束符单行注释多行注释 结束符输入和输出输出输入 变量声明赋值变量初始化更新变量 关键字变量名命名规则 常量数据类型…

2023年11个最佳免费WordPress主题

如果您刚刚开始使用 WordPress&#xff0c;您可能会很自然地认为&#xff0c;只要免费的WordPress主题看起来像您想要的网站主题&#xff0c;那么它就很合适。不幸的是&#xff0c;事情并没有那么简单。这就是为什么在今天的文章中&#xff0c;我们概述了一份可靠的标准清单&am…

投资房产的理由与好处,投资买房的方法与技巧

一、教程描述 本套买房教程&#xff0c;大小2.15G&#xff0c;共有23个文件。 二、教程目录 00.她23岁北漂月薪600&#xff0c;7年后50万在京买了第一套房&#xff0c;如今身价上千万.mpg 01.这个游戏&#xff0c;有些人输了所有钱&#xff0c;一辈子也不明白这个道理.mpg …

脉冲幅度调制信号的功率谱计算

本篇文章是博主在通信等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对人工智能等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在通信领域笔记&#xf…

Hibernate批量处理数据

概念&#xff1a; 批量处理数据是指在一个事务场景中处理大量数据。 在应用程序中难以避免进行批量操作&#xff0c;Hibernate提供了以下方式进行批量处理数据&#xff1a; (1)使用HQL进行批量操作 数据库层面 executeUpdate() (2)使用JDBC API进行批量操作 数据库层面 …

P6 C++控制流语句(continue, break, return)

前言 今天我们讲的是控制流语句&#xff0c;本期内容是上期课程的延续。 控制流语句一般与循环语句一起工作&#xff0c;它们让我们可以更好的控制这些循环的实际运行。 我们有三个主要的控制流语句可以使用&#xff0c;continue 、break 和 return&#xff0c;它们有不同的…

Nginx模块开发之http handler实现流量统计(1)

文章目录 一、handler简介二、Nginx handler模块开发2.1、示例代码2.2、编写config文件2.3、编译模块到Nginx源码中2.4、修改conf文件2.5、执行效果 三、Nginx的热更新总结 一、handler简介 Handler模块就是接受来自客户端的请求并产生输出的模块。 配置文件中使用location指令…

UML建模图文详解教程06——顺序图

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl本文参考资料&#xff1a;《UML面向对象分析、建模与设计&#xff08;第2版&#xff09;》吕云翔&#xff0c;赵天宇 著 顺序图概述 顺序图(sequence diagram&#xff0c;也…