写点什么

一文详解 Redis 中 BigKey、HotKey 的发现与处理

作者:冉然学Java
  • 2022 年 7 月 26 日
  • 本文字数:6122 字

    阅读完需:约 20 分钟

一 前言

在 Redis 的使用过程中,我们经常会遇到 BigKey(下文将其称为“大 key”)及 HotKey(下文将其称为“热 key”)。大 Key 与热 Key 如果未能及时发现并进行处理,很可能会使服务性能下降、用户体验变差,甚至引发大面积故障。

二 大 Key 与热 Key 的定义

我们经常能够在公司内部的 Redis 开发使用规范手册,或网络中大量的 Redis 最佳实践文章里看到有关大 Key、热 Key 的定义,然而这些资料中的大 Key 热 Key 判定标准却不尽相同,但可以明确的是,它们的判定维度是一致的:大 Key 通常都会以数据大小与成员数量来判定,而热 Key 则以其接收到的请求频率、数量来判定。

1 什么是大 Key

通常我们会将含有较大数据或含有大量成员、列表数的 Key 称之为大 Key,下面我们将用几个实际的例子对大 Key 的特征进行描述:

  • 一个 STRING 类型的 Key,它的值为 5MB(数据过大)

  • 一个 LIST 类型的 Key,它的列表数量为 20000 个(列表数量过多)

  • 一个 ZSET 类型的 Key,它的成员数量为 10000 个(成员数量过多)

  • 一个 HASH 格式的 Key,它的成员数量虽然只有 1000 个但这些成员的 value 总大小为 100MB(成员体积过大)

需要注意的是,在以上的例子中,为了方便理解,我们对大 Key 的数据、成员、列表数给出了具体的数字。为了避免误导,在实际业务中,大 Key 的判定仍然需要根据 Redis 的实际使用场景、业务场景来进行综合判断。

2 什么是热 Key

在某个 Key 接收到的访问次数、显著高于其它 Key 时,我们可以将其称之为热 Key,常见的热 Key 如:

  • 某 Redis 实例的每秒总访问量为 10000,而其中一个 Key 的每秒访问量达到了 7000(访问次数显著高于其它 Key)

  • 对一个拥有上千个成员且总大小为 1MB 的 HASH Key 每秒发送大量的 HGETALL(带宽占用显著高于其它 Key)

  • 对一个拥有数万个成员的 ZSET Key 每秒发送大量的 ZRANGE(CPU 时间占用显著高于其它 Key)

三 大 Key 与热 Key 带来的问题

在 Redis 的使用中,大 Key 及热 Key 会给 Redis 带来各种各样的问题,而最常见的问题为性能下降、访问超时、数据不均衡等。


1 大 Key 带来的常见问题

  • Client 发现 Redis 变慢;

  • Redis 内存不断变大引发 OOM,或达到 maxmemory 设置值引发写阻塞或重要 Key 被逐出;

  • Redis Cluster 中的某个 node 内存远超其余 node,但因 Redis Cluster 的数据迁移最小粒度为 Key 而无法将 node 上的内存均衡化;

  • 大 Key 上的读请求使 Redis 占用服务器全部带宽,自身变慢的同时影响到该服务器上的其它服务;

  • 删除一个大 Key 造成主库较长时间的阻塞并引发同步中断或主从切换;


2 热 Key 带来的常见问题

  • 热 Key 占用大量的 Redis CPU 时间使其性能变差并影响其它请求;

  • Redis Cluster 中各 node 流量不均衡造成 Redis Cluster 的分布式优势无法被 Client 利用,一个分片负载很高而其它分片十分空闲从而产生读/写热点问题;

  • 在抢购、秒杀活动中,由于商品对应库存 Key 的请求量过大超出 Redis 处理能力造成超卖;

  • 热 Key 的请求压力数量超出 Redis 的承受能力造成缓存击穿,此时大量强求将直接指向后端存储将其打挂并影响到其它业务;

四 大 Key 与热 Key 的常见产生原因

业务规划不足、Redis 不正确的使用、无效数据的堆积、访问突增等都会产生大 Key 与热 Key,如:

  1. 将 Redis 用在并不适合其能力的场景,造成 Key 的 value 过大,如使用 String 类型的 Key 存放大体积二进制文件型数据(大 Key);

  2. 业务上线前规划设计考虑不足没有对 Key 中的成员进行合理的拆分,造成个别 Key 中的成员数量过多(大 Key);

  3. 没有对无效数据进行定期清理,造成如 HASH 类型 Key 中的成员持续不断的增加(大 Key);

  4. 预期外的访问量陡增,如突然出现的爆款商品、访问量暴涨的热点新闻、直播间某大主播搞活动带来的大量刷屏点赞、游戏中某区域发生多个工会间的战斗涉及大量玩家等(热 Key);

  5. 使用 LIST 类型 Key 的业务消费侧代码故障,造成对应 Key 的成员只增不减(大 Key);

五 找出 Redis 中的大 Key 与热 Key

大 Key 与热 Key 的分析并不困难,我们有多种途径和手段来对 Redis 中的 Key 进行分析并找出其中的“问题”Key,如 Redis 的内置功能、开源工具、阿里云 Redis 控制台中的 Key 分析功能等。


1 使用 Redis 内置功能发现大 Key 及热 Key

Redis 内置的一些命令、工具都可以帮助我们来发现这些问题 Key。当你对 Redis 的大 Key 热 Key 已有明确的分析目标时,可以通过如下命令对对应 Key 进行分析。


通过 Redis 内置命令对目标 Key 进行分析

可能你会选择使用 debug object 命令对 Key 进行分析。该命令能够根据传入的对象(Key 的名称)来对 Key 进行分析并返回大量数据,其中 serializedlength 的值为该 Key 的序列化长度,你可能会选择通过该数据来判断对应 Key 是否符合你的大 Key 判定标准。

需要注意的是,Key 的序列化长度并不等同于它在内存空间中的真实长度,此外,debug object 属于调试命令,运行代价较大,并且在其运行时,进入 Redis 的其余请求将会被阻塞直到其执行完毕。而该命令的运行的时间长短取决于传入对象(Key 名)序列化长度的大小,因此,在线上环境中并不推荐使用该命令来分析大 Key,这可能引发故障。

Redis 自 4.0 起提供了 MEMORY USAGE 命令来帮助分析 Key 的内存占用,相对 debug object 它的执行代价更低,但由于其时间复杂度为 O(N)因此在分析大 Key 时仍有阻塞风险。

我们建议通过风险更低方式来对 Key 进行分析,Redis 对于不同的数据结构提供了不同的命令来返回其长度或成员数量,如下表:


通过以上 Redis 内置命令我们可以方便且安全的对 Key 进行分析而不会影响线上服务,但由于它们返回的结果非 Key 的真实内存占用数据,因此不够精确,仅可作为参考。


通过 Redis 官方客户端 redis-cli 的 bigkeys 参数发现大 Key

如果你并无明确的目标 Key 用于分析,而是希望通过工具找出整个 Redis 实例中的大 Key,此时 redis-cli 的 bigkeys 参数能够方便的帮你实现这个目标。

Redis 提供了 bigkeys 参数能够使 redis-cli 以遍历的方式分析整个 Redis 实例中的所有 Key 并汇总以报告的方式返回结果。该方案的优势在于方便及安全,而缺点也非常明显:分析结果不可定制化。

bigkeys 仅能分别输出 Redis 六种数据结构中的最大 Key,如果你想只分析 STRING 类型或是找出全部成员数量超过 10 的 HASH Key,那么 bigkeys 在此类需求场景下将无能为力。

GitHub 上有大量的开源项目能够实现 bigkeys 的加强版使结果能够按照配置定制化输出,另外你可也以动手使用 SCAN + TYPE 并配合上文表格中的命令自己实现一个 Redis 实例级的大 Key 分析工具。

同样,该方案的实现方式及返回结果使其不具备精确性与实时性,建议仅作为参考。


通过 Redis 官方客户端 redis-cli 的 hotkeys 参数发现热 Key

Redis 自 4.0 起提供了 hotkeys 参数来方便用户进行实例级的热 Key 分析功,该参数能够返回所有 Key 的被访问次数,它的缺点同样为不可定制化输出报告,大量的信息会使你在分析结果时复杂度较大,另外,使用该方案的前提条件是将 redis-server 的 maxmemory-policy 参数设置为 LFU。


通过业务层定位热 Key

指向 Redis 的每一次访问都来自业务层,因此我们可以通过在业务层增加相应的代码对 Redis 的访问进行记录并异步汇总分析。该方案的优势为能够准确并及时的分析出热 Key 的存在,缺点为业务代码复杂度的增加,同时可能会降低一些性能。


使用 monitor 命令在紧急情况时找出热 Key

Redis 的 monitor 命令能够忠实的打印 Redis 中的所有请求,包括时间信息、Client 信息、命令以及 Key 信息。在发生紧急情况时,我们可以通过短暂执行 monitor 命令并将输出重定向至文件,在关闭 monitor 命令后通过对文件中请求进行归类分析即可找出这段时间中的热 Key。

由于 monitor 命令对 Redis 的 CPU、内存、网络资源均有一定的占用。因此,对于一个已处于高压状态的 Redis,monitor 可能会起到雪上加霜的作用。同时,这种异步收集并分析的方案的时效性较差,并且由于分析的精确度取决于 monitor 的执行时间,因此在多数无法长时间执行该命令的线上场景中本方案的精确度也不够好。


2 使用开源工具发现大 Key

Redis 的高度流行使我们能够方便的找到大量开源方案来解决我们当前遇到的难题:在不影响线上服务的同时得到精确的分析报告。


使用 redis-rdb-tools 工具以定制化方式找出大 Key

如果你希望按照自己的标准精确的分析一个 Redis 实例中所有 Key 的真实内存占用并避免影响线上服务,在分析结束后得到一份简洁易懂的报告,redis-rdb-tools 是非常好的选择。

该工具能够对 Redis 的 RDB 文件进行定制化的分析,但由于分析 RDB 文件为离线工作,因此对线上服务不会有任何影响,这是它的最大优点但同时也是它的最大缺点:离线分析代表着分析结果的较差时效性。对于一个较大的 RDB 文件,它的分析可能会持续很久很久。


3 依靠公有云的 Redis 分析服务发现大 Key 及热 Key

如果你期望能够实时的对 Redis 实例中的所有 Key 进行分析并发现当前存在的大 Key 及热 Key、了解 Redis 在运行时间线中曾出现过哪些大 Key 热 Key,使自己对整个 Redis 实例的运行状态有一个全面而又准确的判断,那么公有云的 Redis 控制台将能满足这个需求。


阿里云 Redis 控制台中的 CloudDBA

CloudDBA 是阿里云的数据库智能服务系统,它支持 Redis 大 Key 与热 Key 的实时分析、发现。

大 Key 及热 Key 分析底层为阿里云 Redis 内核的 Key 分析功能,该功能通过 Redis 内核直接发现并输出大 Key 热 Key 的相关信息,因此,该功能的分析结果准确性高效且对性能几乎无任何影响,你可以通过点击 CloudDBA 中的“Key 分析”进入该功能,如下图 1-1:

图 1-1:阿里云 Redis 控制台 CloudDBA

Key 分析功能共有两个页面,它们允许在不同的时间维度对对应 Redis 实例中的 Key 进行分析:

  • 实时:对当前实例立即开始分析当前实例,展示当前存在的所有大 Key 及热 Key。

  • 历史:展示该实例近期曾出现过的大 Key 及热 Key,在历史页面中,所有出现过的大 Key 及热 Key 都会被记录,哪怕这些 Key 当前已经不存在。该功能能够很好的反映 Redis 的历史 Key 状态,帮助追溯过去或现场已遭破坏的问题。

六 大 Key 与热 Key 的处理

现在,我们已经通过多种手段找到了 Redis 中的问题 Key,那么我们应当立即着手对他们进行处理,避免它们在之后的时间中引发问题。


1 大 Key 的常见处理办法

对大 Key 进行拆分

如将一个含有数万成员的 HASH Key 拆分为多个 HASH Key,并确保每个 Key 的成员数量在合理范围,在 Redis Cluster 结构中,大 Key 的拆分对 node 间的内存平衡能够起到显著作用。


对大 Key 进行清理

将不适合 Redis 能力的数据存放至其它存储,并在 Redis 中删除此类数据。需要注意的是,我们已在上文提到一个过大的 Key 可能引发 Redis 集群同步的中断,Redis 自 4.0 起提供了 UNLINK 命令,该命令能够以非阻塞的方式缓慢逐步的清理传入的 Key,通过 UNLINK,你可以安全的删除大 Key 甚至特大 Key。


时刻监控 Redis 的内存水位

突然出现的大 Key 问题会让我们措手不及,因此,在大 Key 产生问题前发现它并进行处理是保持服务稳定的重要手段。我们可以通过监控系统并设置合理的 Redis 内存报警阈值来提醒我们此时可能有大 Key 正在产生,如:Redis 内存使用率超过 70%,Redis 内存 1 小时内增长率超过 20%等。

通过此类监控手段我们可以在问题发生前解决问题,如:LIST 的消费程序故障造成对应 Key 的列表数量持续增长,将告警转变为预警从而避免故障的发生。


对失效数据进行定期清理

例如我们会在 HASH 结构中以增量的形式不断写入大量数据而忽略了这些数据的时效性,这些大量堆积的失效数据会造成大 Key 的产生,可以通过定时任务的方式对失效数据进行清理。在此类场景中,建议使用 HSCAN 并配合 HDEL 对失效数据进行清理,这种方式能够在不阻塞的前提下清理无效数据。


使用阿里云的 Tair(Redis 企业版)服务避开失效数据的清理工作

如果你的 HASH Key 过多,同时存在大量的成员失效需要被清理的问题。由于大量 Key 与大量失效数据的叠加,在此类场景中定时任务已无法做到对无效数据进行及时的清理,阿里云的 Tair 服务能够很好的解决此类问题。

Tair 是阿里云的 Redis 企业版,它在具备 Redis 所有特性(包括 Redis 的高性能特点)的同时提供了大量额外的高级功能。

TairHash 是一种可为 field 设置过期时间和版本的 hash 类型数据结构,它不但和 Redis Hash 一样支持丰富的数据接口和高处理性能,还改变了 hash 只能为 key 设置过期时间的限制:TairHash 允许为 field 设置过期时间和版本。这极大地提高了 hash 数据结构的灵活性,简化了很多场景下的业务开发工作。

TairHash 使用高效的 Active Expire 算法,实现了在对响应时间几乎无影响的前提下,高效完成对 field 过期判断和删除的功能。此类高级功能的合理使用能够解放大量 Redis 的运维、故障处理工作并降低业务的代码复杂度,让运维将精力投入到其它更有价值的工作中,让研发有更多的时间来写更有价值的代码。


2 热 Key 的常见处理办法

在 Redis Cluster 结构中对热 Key 进行复制

在 Redis Cluster 中,热 Key 由于迁移粒度问题造成请求无法打散使单一 node 的压力无法下降。此时可以将对应热 Key 进行复制并迁移至其他 node,例如为热 Key foo 复制出 3 个内容完全一样的 Key 并名为 foo2,foo3,foo4,然后将这三个 Key 迁移到其他 node 来解决单一 node 的热 Key 压力。

该方案的缺点在于代码需要联动修改,同时,Key 一变多带来了数据一致性挑战:由更新一个 Key 演变为需要同时更新多个 Key,在很多时候,该方案仅建议用来临时解决当前的棘手问题。


使用读写分离架构


如果热 Key 的产生来自于读请求,那么读写分离是一个很好的解决方案。在使用读写分离架构时可以通过不断的增加从节点来降低每个 Redis 实例中的读请求压力。

然而,读写分离架构在业务代码复杂度增加的同时,同样带来了 Redis 集群架构复杂度的增加:我们不仅要为多个从节点提供转发层(如 Proxy,LVS 等)来实现负载均衡,还要考虑从节点数量显著增加后带来的故障率增加的问题,Redis 集群架构变更的同时为监控、运维、故障处理带来了更大的挑战。

但是,这一切在阿里云 Redis 服务中显得极为简单,阿里云 Redis 服务以开箱即用的方式提供服务。同时,在业务的发展发生变化时,阿里云的 Redis 服务允许用户通过变配的方式调整集群架构来轻松应对,如:主从转变为读写分离,读写分构转变为集群,主从转变为支持读写分离的集群,以及由社区版直接转变为支持大量高级特性的企业版 Redis(Tair)。

读写分离架构同样存在缺点,在请求量极大的场景下,读写分离架构会产生不可避免的延迟,此时会有读取到脏数据的问题,因此,在读写压力都较大写对数据一致性要求很高的场景下,读写分离架构并不合适。


使用阿里云 Tair 的 QueryCache 特性


QueryCache 是阿里云 Tair(Redis 企业版)服务的企业级特性之一,它的原理如下图 2-1:

图 2-1:Tair QueryCache 原理


阿里云数据库 Redis 会根据高效的排序和统计算法识别出实例中存在的热点 Key,开启该功能后,Proxy 点会根据设定的规则缓存热点 Key 的请求和查询结果(仅缓存热点 Key 的查询结果,无需缓存整个 Key),当在缓存有效时间内收到相同的请求时 Proxy 会直接返回结果至客户端,无需和后端的 Redis 分片执行交互。在提升读取速度的同时,降低了热点 Key 对数据分片的性能影响,避免发生请求倾斜。


至此,来自客户端的同样的请求无需再与 Proxy 后端的 Redis 进行交互而由 Proxy 直接返回数据,指向热 Key 的请求由一个 Redis 节点承担转为多个 Proxy 共同承担,能够大幅度降低 Redis 节点的热 Key 压力,同时 Tair 的 QueryCache 功能还提供了大量的命令来方便用户查看、管理,如通过 querycache keys 命令查看所有被缓存热 Key,通过 querycache listall 获取所有已缓存的所有命令等。


Tair QueryCache 智能化的热 Key 判定与缓存联动功同样能够降低运维及研发的工作负担。


与传统的 Redis 同步中间件相比,阿里云 Redis 全球分布式缓存具有高可靠性、高吞吐低延迟、同步正确性高等特点。

用户头像

冉然学Java

关注

还未添加个人签名 2022.07.07 加入

努力学好Java、爱生活、爱旅游的冉冉; 分享自己工作上的经验,交流、共进步、共成长!

评论

发布
暂无评论
一文详解 Redis 中 BigKey、HotKey 的发现与处理_Java_冉然学Java_InfoQ写作社区