写点什么

Redis 中的 Big Key 问题:排查与解决思路

作者:码农BookSea
  • 2023-10-09
    浙江
  • 本文字数:4723 字

    阅读完需:约 15 分钟

Redis中的Big Key问题:排查与解决思路

本文已收录至 GitHub,推荐阅读 👉 Java随想录

微信公众号:Java 随想录


原创不易,注重版权。转载请注明原作者和原文链接


在处理大型数据时,Redis 作为我们的非关系型数据库经常出现在解决方案之中。然而,在使用 Redis 的过程中,有一些问题可能会悄无声息地影响我们的系统性能,其中最具代表性的就是 Big Key 问题。


这个问题往往被低估,Big Key 会对 Redis 的效率和整体性能产生重大影响。在本文中,我们将深入探索 Big Key 问题的源头,讨论它如何影响系统性能,并提供相应的解决策略。通过了解和解决 Big Key 问题,我们可以更有效地利用 Redis,优化我们的系统并提高性能。

Big Key 问题介绍

在 Redis 中,每个 key 都有一个对应的 value,如果某个 key 的 value 过大,就会导致 Redis 的性能下降或者崩溃。


因为 Redis 需要将大 key 全部加载到内存中,这会占用大量的内存空间,会降低 Redis 的响应速度,这个问题被称为 Big Key 问题。


不要小看这个问题,它可是能让你的 Redis 瞬间变成“乌龟”,由于 Redis 单线程的特性,操作 Big Key 的通常比较耗时,也就意味着 Big Key 阻塞 Redis 的可能性很大,这样会造成客户端阻塞或者引起故障切换,有可能导致“慢查询”或其他连锁反应。


一般而言,下面这两种情况可以被称为 Big Key:


  • String 类型的 key 对应的 value 超过 10 MB。

  • list、set、hash、zset 等集合类型,集合元素个数超过 5000 个。


以上对 Big Key 的判断标准并不唯一,只是一个大体的标准。在实际业务开发中,对 Big Key 的判断是需要根据具体的使用场景做不同的判断。比如操作某个 key 导致请求响应时间变慢,那么这个 key 就可以判定成 Big Key。


在 Redis 中,Big Key 通常是由以下几种原因导致的:


  • 对象序列化后的大小过大。

  • 存储大量数据的容器,如 set、list 等。

  • 大型数据结构,如 bitmap、hyperloglog 等。


如果不及时处理这些大 key,它们会逐渐消耗 Redis 服务器的内存资源,最终导致 Redis 崩溃。

Big Key 问题排查

当出现 Redis 性能急剧下降的情况时,很可能是由于存在大 key 导致的。在排除大 key 问题时,可以考虑采取以下几种方法:

BIGKEYS 命令

Redis 自带的 BIGKEYS 命令可以查询当前 Redis 中所有 key 的信息,对整个数据库中的键值对大小情况进行统计分析。


比如说,统计每种数据类型的键值对个数以及平均大小。


此外,这个命令执行后,会输出每种数据类型中最大的 big key 的信息,对于 String 类型来说,会输出最大 big key 的字节长度,对于集合类型来说,会输出最大 big key 的元素个数。


BIGKEYS命令会扫描整个数据库,这个命令本身会阻塞 Redis,找出所有的大键,并将其以一个列表的形式返回给客户端。


命令格式如下:


$ redis-cli --bigkeys
复制代码


返回示例如下:


# Scanning the entire keyspace to find biggest keys as well as# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'a' with 3 bytes[05.14%] Biggest list found so far 'b' with 100004 items[35.77%] Biggest string found so far 'c' with 6 bytes[73.91%] Biggest hash found so far 'd' with 3 fields
-------- summary -------
Sampled 506 keys in the keyspace!Total key length in bytes is 3452 (avg len 6.82)
Biggest string found 'c' has 6 bytesBiggest list found 'b' has 100004 itemsBiggest hash found 'd' has 3 fields
504 strings with 1403 bytes (99.60% of keys, avg size 2.78)1 lists with 100004 items (00.20% of keys, avg size 100004.00)0 sets with 0 members (00.00% of keys, avg size 0.00)1 hashs with 3 fields (00.20% of keys, avg size 3.00)0 zsets with 0 members (00.00% of keys, avg size 0.00)
复制代码


解读下返回结果,从这个结果中可以看出:


  • Redis 中样本了 506 个键。

  • 这 506 个键总共占用了 3452 字节,平均每个键占用 6.82 字节。

  • 最大的字符串键是'c',值有 6 字节。

  • 最大的列表键是'b',有 100004 个元素。

  • 最大的哈希键是'd',有 3 个字段。

  • 有 504 个字符串键,总共 1403 字节,占所有键的 99.60%,平均每个字符串键大小为 2.78 字节。

  • 有 1 个列表键,包含 100004 个元素,占所有键的 0.20%,平均每个列表键大小为 100004 个元素。

  • 没有集合(set)键。

  • 有 1 个哈希键,包含 3 个字段,占所有键的 0.20%,平均每个哈希键大小为 3 个字段。

  • 没有有序集合(zset)键。


这些信息可以帮助你理解 Redis 数据库的使用状态,以便进行相应的优化或调整。


需要注意的是,由于BIGKEYS命令需要扫描整个数据库,所以它可能会对 Redis 实例造成一定的负担。在执行这个命令之前,请确保你的 Redis 实例有足够的资源来处理它,建议在从节点执行

Debug Object

如果我们找到了 Big Key,就需要对其进行进一步的分析。我们可以使用命令debug object key查看某个 key 的详细信息,包括该 key 的 value 大小等。这时候你就可以“窥探”Redis 的内部,看看到底是哪个 key 太大导致的问题。


Debug Object 命令是一个调试命令,当 key 存在时,返回有关信息。 当 key 不存在时,返回一个错误。


redis 127.0.0.1:6379> DEBUG OBJECT keyValue at:0xb6838d20 refcount:1 encoding:raw serializedlength:9 lru:283790 lru_seconds_idle:150
redis 127.0.0.1:6379> DEBUG OBJECT key(error) ERR no such key
复制代码


第一次运行命令时,返回了 key 对应的具体信息。这些值的意思如下:


  • Value at:0xb6838d20:key 所在的内存地址。

  • refcount:1:引用计数,表示该对象被引用的次数。

  • encoding:raw:编码类型,这里是 raw ,表示这个字符串对象的编码类型。

  • serializedlength:9:序列化后的长度。

  • lru:283790:LRU (Least Recently Used)信息,即最近最少使用算法的相关信息,在内存淘汰策略中会用到。

  • lru_seconds_idle:150:该 key 已空闲多久(单位为秒),也就是自从最后一次访问已经过去多少秒。


第二次运行命令时,返回了 (error) ERR no such key,说明在 Redis 中没有找到名为 'key' 的键。

memory usage

在 Redis4.0 之前,只能通过DEBUG OBJECT命令估算 key 的内存使用(字段 serializedlength),但 DEBUG OBJECT 命令是存在误差的。


4.0 版本及以上,更推荐使用memory usag命令。


memory usage 命令使用非常简单,格式为:memory usage key


如果当前 key 存在,则返回 key 的 value 实际使用内存估算值,如果 key 不存在,则返回 nil。


127.0.0.1:6379> set k1 value1OK127.0.0.1:6379> memory usage k1    //这里k1 value占用57字节内存(integer) 57127.0.0.1:6379> memory usage aaa  // aaa键不存在,返回nil.(nil)
复制代码


对于除 String 类型之外的类型,memory usage 命令采用抽样的方式,默认抽样 5 个元素,所以计算是近似值,我们也可以手动指定抽样的个数。


示例说明:生成一个 100w 个字段的 hash 键:hkey,每字段的 value 长度是从 1~1024 字节的随机值。


127.0.0.1:6379> hlen hkey    // hkey有100w个字段,每个字段的value长度介于1~1024个字节(integer) 1000000127.0.0.1:6379> MEMORY usage hkey   //默认SAMPLES为5,分析hkey键内存占用521588753字节(integer) 521588753127.0.0.1:6379> MEMORY usage hkey SAMPLES  1000 //指定SAMPLES为1000,分析hkey键内存占用617977753字节(integer) 617977753127.0.0.1:6379> MEMORY usage hkey SAMPLES  10000 //指定SAMPLES为10000,分析hkey键内存占用624950853字节(integer) 624950853
复制代码


要想获取 key 较精确的内存值,就指定更大抽样个数。但是抽样个数越大,占用 cpu 时间分片就越大。

redis-rdb-tools

redis-rdb-tools 是一个 python 的解析 rdb 文件的工具,在分析内存的时候,我们主要用它生成内存快照。可以把 rdb 快照文件生成 CSV 或 JSON 文件,也可以导入到 MySQL 生成报表来分析。


使用 PYPI 安装


pip install rdbtools
复制代码


生成内存快照


rdb -c memory dump.rdb > memory.csv
复制代码


在生成的 CSV 文件中主要有以下几列:


  • database key 在 Redis 的 db

  • type key 类型

  • key key 值

  • size_in_bytes key 的内存大小

  • encoding value 的存储编码形式

  • num_elements key 中的 value 的个数

  • len_largest_element key 中的 value 的长度


可以在 MySQL 中新建表然后导入进行分析,然后可以直接通过 SQL 语句进行查询分析。


CREATE TABLE `memory` (     `database` int(128) DEFAULT NULL,     `type` varchar(128) DEFAULT NULL,     `KEY` varchar(128),     `size_in_bytes` bigint(20) DEFAULT NULL,     `encoding` varchar(128) DEFAULT NULL,     `num_elements` bigint(20) DEFAULT NULL,     `len_largest_element` varchar(128) DEFAULT NULL,     PRIMARY KEY (`KEY`) );
复制代码


例如,查询内存占用最高的 3 个 key:


mysql> SELECT * FROM memory ORDER BY size_in_bytes DESC LIMIT 3;+----------+------+-----+---------------+-----------+--------------+---------------------+| database | type | key | size_in_bytes | encoding  | num_elements | len_largest_element |+----------+------+-----+---------------+-----------+--------------+---------------------+|        0 | set  | k1  |        624550 | hashtable |        50000 | 10                  ||        0 | set  | k2  |        420191 | hashtable |        46000 | 10                  ||        0 | set  | k3  |        325465 | hashtable |        38000 | 10                  |+----------+------+-----+---------------+-----------+--------------+---------------------+3 rows in set (0.12 sec)
复制代码

Big Key 问题解决思路

当发现存在 Big Key 问题时,我们需要及时采取措施来解决这个问题。下面列出几种可行的解决思路:

分割大 key

将 Big Key 拆分成多个小 key。这个方法比较简单,但是需要修改应用程序的代码。就像是把一个大蛋糕切成小蛋糕一样,有点费力,但是可以解决问题。


或者尝试将 Big Key 转换成 Redis 的其他数据结构。例如,将 Big Key 转换成 Hash,List 或者 Set 等数据结构。

对象压缩

如果大 key 的产生原因主要是由于对象序列化后的体积过大,我们可以考虑使用压缩算法来减小对象的大小。需要在客户端使用一些压缩算法对数据进行压缩和解压缩操作,例如 LZF、Snappy 等。

直接删除

如果你使用的是 Redis 4.0+的版本,可以直接使用 unlink命令去异步删除大 key。4.0 以下的版本 可以考虑使用 scan命令,分批次删除。


无论采用哪种方法,日常使用中都需要注意以下几点:


  1. 避免使用过大的 value。如果需要存储大量的数据,可以将其拆分成多个小的 value。就像是吃饭一样,一口一口的吃,不要贪多嚼不烂。

  2. 避免使用不必要的数据结构。例如,如果只需要存储一个字符串,就不要使用 Hash 或者 List 等数据结构。

  3. 定期清理过期的 key。如果 Redis 中存在大量的过期 key,就会导致 Redis 的性能下降。就像是家里的垃圾,需要定期清理。

  4. 对象压缩。


最后,结束本文时,我们要明确的是,Redis Big Key 问题是所有使用 Redis 作为数据存储方案的开发者都需要密切关注的重要话题。大 Key 可能会对 Redis 的性能产生严重影响,或者导致意外的内存问题。


因此,开发者应该充分利用现有的工具和策略来检测和避免 Big Key。在使用 Redis 时,需要注意避免使用过大的 value 和不必要的数据结构,以及定期清理过期的 key。


另外,我们还应持续探索更高效、更可靠的解决方案,来优化我们的 Redis 实例,使其更稳定地为我们的应用提供服务。最后,不断学习和实践才是提高我们对 Redis 使用的理解,并准确处理 Redis Big Key 问题的最佳方式。




感谢阅读,如果本篇文章有任何错误和建议,欢迎给我留言指正。


老铁们,关注我的微信公众号「Java 随想录」,专注分享 Java 技术干货,文章持续更新,可以关注公众号第一时间阅读。


一起交流学习,期待与你共同进步!

发布于: 3 小时前阅读数: 3
用户头像

码农BookSea

关注

Java开发工程师 2021-12-26 加入

Java开发菜鸟工程师,写博客的初衷是为了沉淀我所学习,累积我所见闻,分享我所体验。希望和更多的人交流学习。

评论

发布
暂无评论
Redis中的Big Key问题:排查与解决思路_Java_码农BookSea_InfoQ写作社区