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

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等。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

快来考试拿证书!KubeSphere 个人技能专业考试认证上线啦!

以容器技术和容器编排为基础的云原生应用&#xff0c;被越来越多的企业用户接受和使用&#xff0c;并且在生产环境中使用容器技术的比例逐年增加。Kubernetes 无疑已经成为容器编排的事实基础&#xff0c;而依托于 Kubernetes 开发的开源容器平台 KubeSphere 也收获了一众拥趸。…

群晖NAS搭建WebDav服务做文件共享,可随时随地远程访问

文章目录 1. 在群晖套件中心安装WebDav Server套件1.1 安装完成后&#xff0c;启动webdav服务&#xff0c;并勾选HTTP复选框 2. 局域网测试WebDav服务2.1 下载RaiDrive客户端2.2 打开RaiDrive&#xff0c;设置界面语言可以选择中文2.3 点击添加按钮&#xff0c;新建虚拟驱动区2…

论文《Unsupervised Dialog Structure Learning》笔记:详解DD-VRNN

D-VRNN模型和DD-VRNN模型 总体架构 离散-可变循环变分自编码器&#xff08;D-VRNN&#xff09;和直接-离散-可变循环变分自编码器&#xff08;DD-VRNN&#xff09;概述。D-VRNN和DD-VRNN使用不同的先验分布来建模 z t z_t zt​之间的转换&#xff0c;如红色实线所示。 x t x_t…

【Effective C++】 (六) 继承与面向对象设计

【六】继承与面向对象设计 条款32 &#xff1a; 确保public继承是"is a"的关系 Item 32: Make sure public inheritance models “is-a”. C面向对象程序设计中&#xff0c;最重要的规则便是&#xff1a;public继承应当是"is-a"的关系。当Derived public继…

设计师不能忽视的几个宝藏图标设计工具

在这个快速变化的时代&#xff0c;设计师对创新和实用工具的需求越来越大。这就要求我们及时跟上潮流&#xff0c;不断探索和尝试最新、最有价值的图标设计工具。只有这样&#xff0c;我们才能在竞争激烈的设计市场中脱颖而出。以下是我们精心挑选的2024年值得一试的图标设计工…

Linux C++ 服务器端这条线怎么走?一年半能做出什么?

Linux C 服务器端这条线怎么走&#xff1f;一年半能做出什么&#xff1f; 既然你是在校学生&#xff0c;而且编程语言和数据结构的基础还不错&#xff0c;我认为应该在《操作系统》和《计算机体系结构》这两门课上下功夫&#xff0c;然后才去读编程方面的 APUE、UNP 等书。 最…

ffmpeg播放器实战(播放器流程)

1.流程图 1.main窗口创建程序窗口 程序窗口构造函数执行下面内容 2.开启播放 3.开启解码 4.开启渲染 5.反馈给ui 本文福利&#xff0c; 免费领取C音视频学习资料包学习路线大纲、技术视频/代码&#xff0c;内容包括&#xff08;音视频开发&#xff0c;面试题&#xff0c;FFmpeg…

vue中怎么根据选择的名称 生成印章图片

项目中需要根据选择的印章名称&#xff0c;动态生成印章 &#xff0c;印章下方显示当前的日期 代码如下 <template><div><label for"name">选择名称&#xff1a;</label><select id"name" v-model"selectedName">…

virtualbox 扩展磁盘后在win10 虚拟机看不到新扩展的空间

造成标题中问题的原因是&#xff0c;扩展的是win10.vdi 的空间&#xff0c;虚拟机使用使用的下边那个以uuid命名的空间&#xff0c;将这个磁盘的虚拟分配空间也调整到150G . 然后在win10的磁盘管理里就可以看到新加的空间了。之后再点相应的盘进行扩展卷操作即可。

Android跨进程传图片或者大数据(解决TransactionTooLargeException)

跨进程传图片方案 直接intent传bitmap使用文件读写intent传递自定义binder&#xff0c;binder中传递image使用网络传输 一、直接intent传bitmap 优势 使用简单 劣势 相关代码可能有侵入性&#xff0c;必须在四大组件中接收。 intent传递数据的总大小是1MB&#xff0c;其中…

SA实战 ·《SpringCloud Alibaba实战》第14章-服务网关加餐:SpringCloud Gateway核心技术

大家好,我是冰河~~ 一不小心《SpringCloud Alibaba实战》专栏都更新到第14章了,再不上车就跟不上了,小伙伴们快跟上啊! 在《SpringCloud Alibaba实战》专栏前面的文章中,我们实现了用户微服务、商品微服务和订单微服务之间的远程调用,并且实现了服务调用的负载均衡。也基…

geemap学习笔记012:如何搜索Earth Engine Python脚本

前言 本节主要是介绍如何查询Earth Engine中已经集成好的Python脚本案例。 1 导入库 !pip install geemap #安装geemap库 import ee import geemap2 搜索Earth Engine Python脚本 很简单&#xff0c;只需要一行代码。 geemap.ee_search()使用方法 后记 大家如果有问题需…

前端处理返回数据为数组对象且对象嵌套数组并重名的数据,合并名称并叠加数据

前端处理返回数据为数组对象且对象嵌套数组并重名的数据&#xff0c;合并名称并叠加数据 var newList[]; var table{}; var dataObj{}; var finalList[]; var tableData[{brName:营业部,dateStr:2023-11-23,tacheArr:[{dealCnt:20,tacheName:奔驰}]},{brName:营业部,dateStr:2…

想自学软件测试?一般人我还是劝你算了吧。。。

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

python+pytest接口自动化:token关联登录这样做,阿里p8都直呼牛逼!!!

在PC端登录公司的后台管理系统或在手机上登录某个APP时&#xff0c;经常会发现登录成功后&#xff0c;返回参数中会包含token&#xff0c;它的值为一段较长的字符串&#xff0c;而后续去请求的请求头中都需要带上这个token作为参数&#xff0c;否则就提示需要先登录。 这其实就…

如何在Linux系统上检测GPU显存和使用情况?

如何在Linux系统上检测GPU显存和使用情况&#xff1f; 在Linux系统上&#xff0c;你可以使用一些命令行工具来检测GPU显存和使用情况。以下是一些常用的方法&#xff1a; 1. 使用nvidia-smi&#xff08;仅适用于NVIDIA GPU&#xff09; 如果你使用的是NVIDIA的显卡&#xff0…

小猪优版的前世今生:从籍籍无名到行业瞩目,再到骤变的风暴中心

1. 前世&#xff1a;籍籍无名到行业新星的崛起 小猪优版在初创时期&#xff0c;并不被大众所知。然而&#xff0c;它凭借对短视频行业的深度洞察&#xff0c;以及独特的商业模式&#xff0c;开始在这个领域崭露头角。它提供了一个平台&#xff0c;不仅助力内容创作者更好地展现…

1688商品详情数据接口(1688.item_get)

1688商品详情数据接口是一种程序化的接口&#xff0c;通过这个接口&#xff0c;商家或开发者可以使用自己的编程技能&#xff0c;对1688平台上的商品信息进行查询、获取和更新。这个接口允许商家根据自身的需求&#xff0c;获取商品的详细信息&#xff0c;例如价格、库存、描述…

中医馆管理系统预约小程序效果如何

人们生活水平提升的同时&#xff0c;无论是工作压力还是自然压力&#xff0c;都给身体带来了一些损伤&#xff0c;如各科常见病、多发病、慢性病及疑难杂症等。中医具有治未病的优势&#xff0c;因此对患者而言&#xff0c;找中医诊治是一个很好的选择&#xff0c;而无论中医院…

财报解读:电商GMV增长30%后,快手将坚守本地生活?

快手逐渐讲好了其高质量成长的故事。 根据财报&#xff0c;快手三季度业绩超出预期&#xff0c;其中&#xff0c;营收279.5亿元&#xff0c;同比增长20.8%&#xff1b;调整后净利润31.7亿元&#xff0c;同比扭亏为盈。 而联系市场环境来看&#xff0c;三季度广告、电商市场较…