免费下载!由 O’Reilly 出版的《NGINX 完全指南》中文版已正式上线 了解详情
写点什么

如何优雅的分析 Redis 里存了啥?

  • 2018-01-29
  • 本文字数:3096 字

    阅读完需:约 10 分钟

Redis 是互联网产品开发中不可缺少的常备武器,它性能高、数据结构丰富、简单易用,但同时也是因为太容易用了,我们的开发同学不管什么数据、不管这数据有多大、不管数据有多少通通塞进去,最后导致的问题就是 Redis 内存使用持续上升,但是又不知道里面的数据是不是有用,是否可以拆分和清理。

为了更好地使用 Redis,除了对 Redis 做一些使用规范,还需要对线上使用的 Redis 有充分的了解。那么问题来了:一个 Redis 的实例用了那么大的内存,里边到底存了啥?都有哪些 key?每个 key 占用了多少空间?

雪球当前有几十个 Redis 集群,近千个 Redis 实例,5T 的内存数据,我们也想要分析业务是否有误用,以提高资源利用率。当然,曾经我们也深深地被这个问题所困扰,今天我就来和大家分享下我是如何解决这个问题的,希望能给各位一些启发。

那有没有什么办法让我们安全高效的看到 Redis 内存消耗的详细报表呢?办法总比问题多,有需求就有解决方案。雪球 SRE 团队针对这个问题实现了一个 Redis 数据可视化平台 RDR (Redis Data Reveal)。RDR 可以非常方便的对 Reids 的内存进行分析,了解一个 Redis 实例里都有哪些 key,哪类 key 占用的空间是多少,最耗内存的 key 有哪些,占比如何,非常直观。

设计思路

我们先梳理下,有什么办法可以拿到 Redis 的所有数据。从我的角度看,大概有以下几种方法,我们分析一下个字的优缺点:

1. 先通过 keys * 命令,拿到所有的 key,然后根据 key 再获取所有的内容。

  • 优点:可以不使用 Redis 机器的硬盘,直接网络传输
  • 缺点:如果 key 数量特别多,keys 命令可能会导致 Redis 卡住影响业务;需要对 Redis 请求非常多次,资源消耗多;遍历数据太慢

2. 开启 aof,通过 aof 文件获取到所有数据。

  • 优点:无需影响 Redis 服务,完全离线操作,足够安全;
  • 缺点:有一些 Redis 实例写入频繁,不适合开启 aof,普适性不强;aof 文件有可能特别大,传输、解析起来太慢,效率低。

3. 使用 bgsave,获取 rdb 文件,解析后获取数据。

  • 优点:机制成熟,可靠性好;文件相对小,传输、解析效率高;
  • 缺点:bgsave 虽然会 fork 子进程,但还是有可能导致主进程卡住一段时间,对业务有产生影响的风险;

以上几种方式我们评估之后,决定采用低峰期在从节点做 bgsave 获取 rdb 文件,相对安全可靠,也可以覆盖所有业务的 Redis 集群。也就是说每个实例每天在低峰期自动生成一个 rdb 文件,即使报表数据有一天的延迟也是可以接受的。

拿到了 rdb 文件就相当于拿到了 Redis 实例的所有数据,接下来就是生成报表的过程了:

  1. 解析 rdb 文件,获取到 Key 和 Value 的内容;
  2. 根据相对应的数据结构及内容,估算内存消耗等;
  3. 统计并生成报表;

逻辑很简单,所以设计思路很清晰。数据流图如下:

我们再看下具体该如何实现,首先是语言选型,雪球 SRE 自研的组件基本都是用 Go 语言做的后端,所以语言选型没什么纠结,直接用 Go。然后就是刚刚说的那几个流程。

1. 解析 RDB

按照 Redis 的协议来做就可以了,这个在 GitHub 上搜索 parse rdb 就可以找到许多解析 rdb 文件的库,拿过来使用即可。我们使用了 https://github.com/cupcake/rdb

2. 估算内存消耗

一条记录会有哪些内存使用呢?

我们知道 Redis 的实现里面有一些基础的数据结构,就是用这些结构来实现了对外暴露的各种数据类型:比如 sds、dict、intset、zipmap、adlist、ziplist、quicklist、skiplist 等等。只要根据这条记录的数据类型,找出使用了哪些数据结构,再计算出这些基础数据结构的内存消耗,再加上数据的内存使用,以及一些额外开销比如过期时间等,就可以估算出一条记录到底使用了多少内存。

但是由于 Redis 做了非常多的优化,同样的一种数据类型,在不同场景下使用的数据结构有可能是不同的。比如 List ,比较老版本的 Redis,会根据 list 元素的数量来决定来使用哪种结构,较短的时候使用 adlist,长之后使用 ziplist,数值可以通过 list-max-ziplist-entries 来配置。

3.2 版本以后全都使用了 quicklist。而不同结构对于内存的使用其实是有区别的,我们计算的时候也没办法拿到具体的配置,所以都按默认配置来计算,最后得出的值是一个估算的值,不过也基本可以反应使用情况了。如果大家对于 Redis 使用的各种数据结构感兴趣,想了解其设计及适用场景,可以多搜索一下相关的资料以及阅读 Redis 源码。

举个计算内存使用的例子:

假如我们通过解析 rdb,获取到了一个 key 为 hello,value 为 world,类型为 string ,ttl 为 1440 的一条记录,它的内存使用是这样的:

  • 一个 dictEntry 的消耗,Redis db 就是一个大 dict,每对 kv 都是其中的一个 entry ;
  • 一 个 robj 的消耗,robj 是为了在同一个 dict 内能够存储不同类型的 value,而使用的一个通用的数据结构,全名是 Redis Object;
  • 存储 key 的 sds 消耗,sds 是 Redis 中存储字符串使用的数据结构;
  • 存储过期时间消耗;
  • 存储 value 的 sds 消耗;

前四项基本是存储任何一个 key 都需要消耗的,最后一项根据 value 的数据结构不同而不同。

  • 一个 dictEntry 有 2 个指针,一个 int64 的内存消耗;
  • 一个 robj 有 1 指针,一个 int,以及几个使用位域的字段共消耗 4 字节;
  • 过期时间也是存储为一个 dictEntry,时间戳为 int64;
  • 存储 sds 需要存储 header 以及字符串长度 +1 的空间,header 长度根据字符串长度不同也会有所不同;

我们根据以上信息可以算出,向操作系统申请这些内存,真正需要多少内存。由于 Redis 支持多种 malloc 算法,我们就按 jemalloc 的分配方式算,这里也是可能存在误差的点。

所以最后 key 为 hello 的这条记录在 64 位操作系统上一共会消耗 92 字节。

其他类型的计算也大致是同样的思路,只不过根据不同的数据结构需要计算不同的内存消耗,计算的时候要记得考虑内存对齐的情况。还有由于 zset 的算法涉及到了随机生成层数,我们也使用同样的算法来随机,但是算出来的值肯定不是精确的,也是一个误差点。

3. 统计计数

终于可以拿到任何一个 key 的内存使用了,哪些是最有意义最有价值的数据呢?

  • top N,毫无疑问最大的前 N 个 key 一定是要关注的;
  • 不同数据类型的 key 数量元素数量分布以及内存使用情况;
  • 按照前缀分类,统一的前缀一般意味着某个特定的业务在使用,计算各个分类的 key 数量及内存使用情况;

这几个需求实现起来也都很容易:

  1. 维护一个小顶堆来存储前 N 个最大的即可,最后取出堆中的数据即可;
  2. 计数即可;
  3. 一般都会有特定的分隔符,比如 :|._ 等字符,按照这些字符切出公共前缀再统计,同时把所有的数字都替换为 0,便于分类;

4. 报表数据

可以每天打开个网页就可以看到某个 Redis 实例的内存使用的详细情况,是件非常幸福的事情,Redis 的内存使用再也不是黑盒。

这个系统上线一年以来对我们优化 Redis 资源使用、提高效率、节约成本提供了非常重要的数据支撑,而且在内部完全自动化,开发同学自己就可以看到当前 Redis 的使用情况是否符合预期,对于保障业务稳定也起到了非常重要的作用。这也是雪球的工程师团队一贯的做法,SRE 提供高效的工具,开发工程师可以自己运维自己的业务系统,可以极大的提高生产效率。

这个项目参考了 redis-rdb-tool 这个开源项目,但是性能上比它高效几倍,为了回馈社区,也希望有机会帮到大家,所以我们决定开源出来。

雪球的内部系统根据自己的特殊场景做了自动化获取 rdb 文件并备份的逻辑,开源出来的版本去除了定制化,只保留了获取到 rdb 之后的分析逻辑以及页面。

项目地址为 https://github.com/xueqiu/rdr 。有任何问题可以提 issue,也可以直接邮件联系我:dongmx@xueqiu.com。

作者介绍

董明鑫,雪球 SRE 工程师,主要负责保障雪球稳定性、提升资源利用率及提高开发效率等方向。

感谢郭蕾对本文的审校。

2018-01-29 16:3914415

评论

发布
暂无评论
发现更多内容

[译] Plaid 应用迁移到 AndroidX 的实践经历,安卓卡顿优化方法

android 程序员 移动开发

“吹Kotlin协程的,可能吹错了,谈谈我认为的高级Android开发到底应该是怎样的

android 程序员 移动开发

“阿里钉钉“Android 岗无缘二面,有幸内推进入“美团“送外

android 程序员 移动开发

【 Flutter 手势探索】我的第二本小册来了,嵌入式音视频方向

android 程序员 移动开发

[译] 充分利用多摄像头 API,30分钟轻松入门flutter

android 程序员 移动开发

_ Dart 里的类型系统,android自定义控件开发入门与实战

android 程序员 移动开发

_Android 项目中 shape 标签的整理和思考,面试官不讲武德

android 程序员 移动开发

【AAC 系列三】深入理解架构组件,androidjetpack视频

android 程序员 移动开发

[译]内存泄露的八种花样,快速从入门到精通

android 程序员 移动开发

阿里云EMAS发布套餐订阅云服务

移动研发平台EMAS

[译][2(1),android开发计算器源码

android 程序员 移动开发

[译] 改善 Android Studio 的构建速度,掌握这6大技能体系

android 程序员 移动开发

可视化全埋点系列文章之元素标识篇

神策技术社区

可视化 全埋点

“我是培训 + 外包出来的(1),flutter与android混合开发

android 程序员 移动开发

“我是培训 + 外包出来的,先收藏了

android 程序员 移动开发

【 FlutterUnit 食用指南】 开源篇,Android自定义View详解

android 程序员 移动开发

【- Flutter 桌面篇 -】 FlutterUnit win版闪亮登场,android入门基础

android 程序员 移动开发

「Android高工面试之旅」疫情失业裸面失败后,深入分析

android 程序员 移动开发

「大厂Android面试解析」20道面试灵魂拷问题,论程序员成长的正确姿势

android 程序员 移动开发

「安卓高级架构师」教你一种更清晰的Android架构,android原生开发技术

android 程序员 移动开发

【Android高级架构师系统学习文章】Android基础-Activity全方位解析

android 程序员 移动开发

[译]掌握Kotlin中的标准库函数_ run、with、let,搞懂开源框架设计思想真的这么重要吗

android 程序员 移动开发

{Android}一行代码起飞,2021年百度Android面试真题

android 程序员 移动开发

{Android}面对最近的话题,kotlin库

android 程序员 移动开发

Logback基本使用

平凡人生

“新常态”下的Android程序员要面对哪些压力,终于有人把安卓程序员必学知识点全整理出来了

android 程序员 移动开发

“终于懂了” 系列,android面试简历模板

android 程序员 移动开发

会议管理系统减少大中型企业的会议矛盾

低代码小观

企业管理 企业应用 管理系统 管理工具 会议管理

[译] 全新 Android 注入器 _ Dagger 2(三),android开发架构方案

android 程序员 移动开发

[译] 改善 Android Studio 的构建速度(1),美团移动端开发工程师

android 程序员 移动开发

[译][2,安卓应用开发项目

android 程序员 移动开发

如何优雅的分析 Redis 里存了啥?_语言 & 开发_董明鑫_InfoQ精选文章