Redis大Key问题的解决方案

Redis大Key问题的解决方案

什么是大Key问题

Redis中某些键(key)所对应的值(value)特别大,或者集合类数据结构(如hash、set、zset、list)中存储的元素数量过多,这就是大Key问题。它分为以下三种类型:

单个字符串类型(String)Key的Value特别大

具体大小标准依据业务场景不同而有所变化,一般认为在普通业务场景下,如果单个String类型的value大于1MB,或者在高并发低延迟场景中大于10KB,就可能被视为大Key。

集合数据类型(如Hash、Set、ZSet、List)中的元素数量过多或总体数据量过大

例如,一个Hash类型Key的成员数量虽只有1000个,但这些成员的Value总大小达到100MB,或者一个ZSet类型的Key成员数量达到10000个,也会被看作是大Key问题。

单个Key的内存占用过高

比如阿里云Redis定义中,一个String类型的Key其值达到5MB,或一个ZSet类型的Key成员数量达到10000个,都被视为大Key。

负面影响

读取成本高

大Key由于体积大,读取时会消耗更多的时间,增加延迟,尤其是在网络传输中,大Key会占用更多的带宽,影响系统的响应速度和网络资源的有效利用。

写操作易阻塞

写入大Key时,由于Redis采用单线程模型处理请求,操作大Key会阻塞其他命令的执行,导致整个Redis服务响应变慢,甚至无法正常响应其他请求。

慢查询与主从同步异常

大Key的读写操作时间长,可能触发Redis的慢查询日志记录,频繁的慢查询会加重服务器负担。同时,在主从复制场景下,大Key的同步也会比小Key慢,可能影响数据的一致性和实时性。

占用更多存储空间,导致逐出与OOM

大Key占据大量内存空间,容易触发Redis的内存淘汰策略,造成重要数据被意外移除(逐出)。在极端情况下,可能会导致Redis实例因内存耗尽而崩溃(OOM)。

集群架构下的内存资源不均衡

在Redis集群中,若某个分片上有大Key,该分片的内存使用率将远高于其他分片,打破集群间内存使用的均衡状态,影响集群的整体性能和稳定性。

影响高并发与低延迟要求的场景

在对时延敏感的应用中,大Key的存在会显著增加请求处理时间,降低系统处理高并发请求的能力。

产生原因

业务设计不合理

最常见的原因是在没有合理拆分的情况下,直接将大量数据(如大的JSON对象或二进制文件数据)存储在一个键中。这种做法忽视了Redis作为内存数据库的特性,没有充分利用其高效处理小数据块的优势。

未能处理Value动态增长问题

随着时间推移,如果持续向某个键的Value中添加数据,而又没有相应的定期删除机制、合理的过期策略或大小限制,Value的大小最终会增长到难以管理的程度。例如,不断累积的微博粉丝列表、热门评论或直播弹幕等场景很容易形成大Key。

程序Bug

有时候,软件开发中的错误可能导致某些键的生命周期超出预期,或者其包含的元素数量异常增长。例如,如果负责消费LIST类型键的业务代码发生故障,可能会导致该Key的成员只增不减,进而形成大Key。

找出大Key

使用redis-cli的--bigkeys参数

这是最直接的方法。通过Redis命令行工具redis-cli,使用--bigkeys参数来扫描Redis实例中的所有Key。它会遍历整个键空间并返回每个数据类型中最大的Key的信息。执行命令如下:

redis-cli --bigkeys

此命令会输出每个数据类型中最大的Key及其相关信息,以及一些整体统计信息,如不同类型Key的数量、平均长度等。

利用Redis RDB Tools

一种更深入和定制化的分析方法是使用Redis RDB Tools这个开源工具。首先,导出Redis的RDB文件,然后使用该工具分析此文件,找出大Key。例如,输出占用内存超过128字节的前5个Keys到CSV文件:

rdb -c memory dump.rdb --bytes 128 --largest 5 -f memory.csv

这样做可以更精确地控制分析条件,比如按大小过滤Key,或者按数量筛选最大的几个Key。

可观测性分析

通过监控工具,跟踪Redis的性能指标,如延迟、吞吐量和错误率,以及分析慢查询日志,也是发现大Key的一种间接方法。如果存在执行时间过长的操作,这可能是大Key导致的。部分云服务提供商还可能提供了直接查看Top Key统计的功能,便于发现内存占用高的Key。

解决方案

那么通过以上手段找出大Key后,就需要分析两个问题🤔:

  1. 确定大Key是否是业务必要的(如果不是,就让它棍😠),能不能通过业务逻辑的优化来处理这个问题
  2. 检查程序Bug,看看是不是代码写的有问题🙂,导致Key的大小不正常

如果以上问题都检查无误,也没有解决大Key问题,可以考虑下面的优化策略:

😋 避免大Key问题

解决问题的最好办法就是解决提出问题的人

在业务设计的初期就应该避免生成大Key,仅仅缓存必要的数据字段。

数据拆分

就像视频分片缓冲一样,可以考虑把大Key分片成小Key进行存储。比如直接存储中国的所有省市区的一些详细信息(招商、气象)肯定会产生大Key(“China”),但如果分片存储,定义一个namespace,然后省、市、区分别去存,就形成了小key,具体说明就是:

  1. 定义namespace:可以将“China”作为一个命名空间。

  2. 省、市、区分别存储:

    • 省级信息可以存储为 China:province:省份ID,值为该省的省级详细信息。
    • 市级信息存储为 China:city:城市ID,值为该市的市级详细信息。
    • 区县级信息存储为 China:district:区域ID,值为该区的区县级详细信息。

这种是靠定量可以解决的。如果是不定量的需求,即Value会增长的,那么就可以考虑依据第一个分片Value有几片,再按照这个num往下分。

不过,分片可能会造成部分写的问题。比如,一个不恰当但好理解的例子。用户提交订单时,需要同时写入这三个分片:

  1. Order:Info:{OrderID} -> 订单基本信息
  2. Order:Items:{OrderID} -> 订单商品列表
  3. Order:Payment:{OrderID} -> 订单支付信息

假设在执行写操作时,发生以下情况:

  1. Order:Info:{OrderID}写入成功
  2. Order:Items:{OrderID}写入成功
  3. Order:Payment:{OrderID}写入失败

这就是部分写问题。那么在设计阶段,可以为每一个Value加入一个版本号,以进行一致性检查,如果有一个版本号没对上,就立马回源,重新读取,然后重新加载并再次尝试写入。

↪️ 换个方向

你有没有考虑过这个数据就不应该用String存储?

比如在网络爬虫中,开发人员可能会用一个很长的String来记录哪些URL已经被访问过,每个字符对应一个URL的hash值,但这样会浪费内存空间。如果用Bitmap来记录访问过的URL,每一位表示一个URL的hash值,这样可以节省大量内存,并且能快速判断URL是否已经访问过。

又或者是用户活跃度统计。如果要记录一个大型网站每天数百万用户的登录活跃情况,对于每一天,可以用一个整数的32位(或64位)比特位来表示当天所有用户是否登录过,其中每位代表一个用户ID是否活跃。如果使用String来存储,即使每个用户活跃状态只需一位表示,也会因为String的编码方式(如UTF-8)而占用更多的空间,每个字符至少占用8位。

🧹 合理清理

只需要设计个方案合理清理就能避免大Key的累积。

  1. 在低峰期删除。可以考虑在业务流量低的时候定时清理缓存。但很明显,这个方案不太合理。假如一整天业务流量都很高,这时候已经产生大Key了呢?
  2. 分批定时定量删除。定量删除是为了防止阻塞。
  3. 异步删除。与del命令不同的是,unlink命令会异步地删除指定的键以及与之相关联的值。 即,它会将要删除的键添加到一个待删除的列表中,并立即返回,不会阻塞客户端。 Redis服务器会在后台异步地删除待删除列表中的键。
  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值