当前位置: 首页 > article >正文

【Redis】Redis大key的危害及解决方案分享

文章目录

  • 一、背景
  • 二、什么是大key
  • 三、大key评价标准
  • 四、大key 产生的原因与场景
  • 五、大key影响与危害
  • 六、大key检查与发现
    • 6.1 使用 --bigkeys参数
    • 6.2 使用scan命令
    • 6.3 使用 memory 命令查看 key 的大小
    • 6.4 使用 Rdbtools 工具包
    • 6.5 代码埋点
    • 6.6 公有云的Redis分析服务
  • 七、大key解决方式
    • 7.1 增加监控告警
    • 7.2 数据结构优化
    • 7.3 清理过期数据
    • 7.4 压缩数据
    • 7.5 拆分存储
      • 7.5.1 按时间/业务拆分
      • 7.5.2 按哈希拆分
  • 八、总结

一、背景

Redis作为后端开发中的一个常用组件,在开发过程中承担着非常重要的作用。在其实际使用过程中,我们常常会面临一些技术挑战,其中常见的问题就包括大key问题。当某些数据量较大的键值对(如富文本等)存储在Redis中时,这些大key的读写操作会明显影响系统的性能,严重时会导致系统出现卡顿的问题。因此,Redis大key的监控与治理是互联网开发中使用Redis时必须面对的一个课题,本文将按照redis大key的定义与评判标准、危害与解决方式的顺序来做个总结。


二、什么是大key

通常来说,Big Key指的就是某个key对应的value很大,占用的Redis空间很大,是value过大的问题。这是很多文章中会提到的一点, 除此之外,这里想强调的是Redis的key的大小同样也会对性能存在隐患 ,而为什么我们会比较少去讨论这一点呢,那是因为key是我们人为直接设置的,其相对来说更可控一点,但是它也同样不可忽视。其实我们从存储结构就不难分析得到上述结论:

根结构为RedisServer,其中包含RedisDB(数据库)。而RedisDB实际上是使用Dict(字典)结构对Redis中的kv进行存储的。这里的key即字符串,value可以是string/hash/list/set/zset这五种对象之一。
Dict字典结构中,存储数据的主题为DictHt,即哈希表。而哈希表本质上是一个DictEntry(哈希表节点)的数组,并且使用链表法解决哈希冲突问题(关于哈希冲突的解决方法可以参考大佬的文章 解决哈希冲突的常用方法分析)。
在这里插入图片描述

所以在实际存储时,key和value都是存储在DictEntry中的。所以基本上来说,大key和大value带来的内存不均和网络IO压力都是一致的,只是key相较于value还多一个做hashcode和比较的过程(链表中进行遍历比较key),会有更多的内存相关开销。

通过redis的key和value存储来看,其大小带来的影响基本一致,都会给系统性能带来不小的影响。


三、大key评价标准

一般认为string类型控制在10KB以内,hash、list、set、zset元素个数不要超过10000个。但在实际业务中,大Key的判定仍然需要根据Redis的实际使用场景、业务场景来进行综合判断,通常都会以数据大小与成员数量来判定。在不同互联网公司中,其对Redis大Key的定义标准也略有差异,例如已知业内公司的评价标准如下:

  • 阿里标准:字符推荐小于10KB,但是他们内网建议是小于1KB,集合个数推荐低于1000;
  • 美团标准:字符推荐不能超过512KB,集合个数不能超过1W个,单个不能超于1M;
  • 携程标准:字符类型大小控制在10KB以内,hash/list/set/zset等包含元素个数建议控制在1000以内,单个集合大小没有限制。

四、大key 产生的原因与场景

大 key 通常是由于下面这些原因产生的:

  • redis数据结构使用不恰当
    • 将Redis用在并不适合其能力的场景,造成key的value过大,如使用String类型的key存放大体积二进制文件型数据(富文本类型数据)
  • 未及时清理垃圾数据
    • 没有对无效数据进行定期清理,造成如Hash类型key中的成员持续不断的增加
  • 对业务预估不准确
    • 业务上线前规划设计考虑不足没有对key中的成员进行合理的拆分,造成个别key中的成员数量过多
  • 明星、网红的粉丝列表、某条热点新闻的评论列表
    • 用List数据结构保存热点新闻的评论列表,因为粉丝数量巨大,热点新闻因为点击率、评论数会很多,这样List集合中存放的元素就会很多,可能导致value过大,进而产生Big Key问题。

引发大Key问题的几个常见场景如下:

  • 队列型应用
    • 将Redis用作队列处理任务,如果队列消费速度跟不上生产速度,队列会越来越大,最终形成"大key"
  • 统计型应用
    • 按天存储某项功能或网站的用户集合信息,如果用户量较大,这类"统计型"数据容易形成"大key"
  • 缓存型应用
    • 业务上线前规划设计考虑不足没有对key中的成员进行合理的拆分,造成个别Key中的成员数量过多

五、大key影响与危害

  • 性能下降
    • 大key的读写操作可能会消耗更多的CPU和内存资源,导致redis的响应时间变慢
  • 内存使用不均衡
    • 在集群环境中,大key可能导致某些数据分片的内存使用率远高于其他分片,造成内存资源分配不均匀
  • 带宽占用
    • 如果大key被频繁访问,可能会占用大量的网络带宽,影响其他服务的性能
  • 阻塞问题
    • 由于redis是单线程的,操作大key可能会造成redis服务阻塞,尤其是在执行清除操作或者过期时间到达时
  • 内存溢出
    • 如果redis实例的内存达到maxmemory参数定义的上限,大key的存在可能导致操作阻塞或者重要key被逐出,甚至引发内存溢出
  • 缓存穿透
    • 大key的频繁清理可能导致缓存穿透问题,影响缓存效率
  • 数据倾斜
    • 在集群架构下,大key可能导致访问倾斜,即扣个数据分片被大量访问,而其他分片处于空闲状态,可能会引起链接数耗尽问题
  • 缓存击穿
    • 热key的请求压力超出redis的承受能力,可能导致缓存击穿,大量请求直接指向后端存储层,造成存储层访问量激增

六、大key检查与发现

6.1 使用 --bigkeys参数

–bigkeys 是 redis 自带的命令,对整个 Key 进行扫描,统计 string,list,set,zset,hash 这几个常见数据类型中每种类型里的最大的 key。String 类型统计的是 value 的字节数,另外 4 种复杂结构的类型统计的是元素个数,不能直观的看出 value 占用字节数,所以 --bigkeys 对分析 string 类型的大 key 是有用的,而复杂结构的类型还需要一些第三方工具(注:元素个数少,不一定 value 不大;元素个数多,也不一定 value 就大)。

–bigkeys 是以 scan 延迟计算的方式扫描所有 key,因此执行过程中不会阻塞 redis,但实例存在大量的 keys 时,命令执行的时间会很长,这种情况建议在 slave 上扫描。
–bigkeys 其实就是找出类型中最大的 key,最大的 key 不一定是大 key,最大的 key 都不超过 10kb 的话,说明不存在大 key。但某种类型如果存在较多的大key (>10kb),只会统计 top1 的那个 key,如果要统计所有大于 10kb 的 key,需要用第三方工具扫描 rdb 持久化文件。

redis-cli -h 127.0.0.1 -p 6379 -a "password" --bigkeys

6.2 使用scan命令

使用 Redis 自带的 scan命令,这个命令会以遍历的方式分析 Redis 实例中的所有 key,并返回整体统计信息,结合其他命令(如List 类型:LLEN 命令;Hash 类型:HLEN 命令;Set 类型:SCARD 命令;Sorted Set 类型:ZCARD 命令)我们可以识别出大Key。scan命令的优势在于它可以在不阻塞Redis实例的情况下进行遍历。

redis-cli --scan --pattern '*' --count 1000

除此之外,其实也可以通过脚本等定期主动扫描Redis键值,当键值超过阈值则记录为BigKey。

import redis
import time

# Redis连接
redis_client = redis.Redis(host='localhost', port=6379) 

# 大键阈值 - 100MB
BIG_KEY_THRESHOLD = 100 * 1024 * 1024

# 扫描间隔 - 1小时
SCAN_INTERVAL = 3600 

def scan_big_keys():
    cursor = '0'
    big_keys = []

    while cursor != 0:
        cursor, keys = redis_client.scan(cursor=cursor, count=100)

        for key in keys:
            size = redis_client.debug_object(key)['serializedlength']
            if size > BIG_KEY_THRESHOLD:
                big_keys.append({'key': key, 'size': size})

    return big_keys

while True:
    big_keys = scan_big_keys()

    if big_keys:
        print(f'Found {len(big_keys)} big keys')
        for bk in big_keys:
            print(f'  {bk["key"]} {bk["size"]}')

    time.sleep(SCAN_INTERVAL)

主要步骤包括:

​ 1.使用SCAN命令渐进式扫描键空间
​ 2.调用DEBUG OBJECT命令获取键值大小
​ 3.比较大小判断是否为大键
​ 4.定期循环扫描这个脚本可以灵活调整大键阈值、扫描间隔等参数。

可以将它部署为持续运行的任务,自动发现Redis中的大键,但是这种方式结合了DEBUG OBJECT命令,其运行代价较大,在其运行时,进入Redis的其余请求将会被阻塞直到其执行完毕。因此,如果使用这种方式进行检测,最好让其在对用户体验影响最小的时候运行(如凌晨两三点)。

6.3 使用 memory 命令查看 key 的大小

在Redis4.0之前,只能通过DEBUG OBJECT命令估算key的内存使用(字段serializedlength),但DEBUG OBJECT命令是存在误差的。Redis4.0以后的版本中可以使用 memory 命令查看 key 的大小。如果当前key存在,则返回key的value实际使用内存估算值,如果key不存在,则返回nil。

redis-cli -h 127.0.0.1 -p 6379 -a password
 
MEMORY USAGE keyname1
(integer) 157481
 
MEMORY USAGE keyname2
(integer) 312583

MEMORY USAGE keyname3
(nil)

6.4 使用 Rdbtools 工具包

Rdbtools 是 Python写的 一个第三方开源工具,用来解析 Redis 快照文件,除了解析 rdb 文件,其还提供了统计单个 key 大小的工具。使用Rdbtools 离线分析工具来扫描RDB持久化文件,优点在于无性能损耗、获取的key信息详细、可选参数多、支持定制化需求,结果信息可选择json或csv格式,后续处理方便,但其缺点是需要离线操作,获取结果时间较长。
①安装

git clone https://github.com/sripathikrishnan/redis-rdb-tools
cd redis-rdb-tools sudo && python setup.py install

②使用
从 dump.rdb 快照文件统计, 将所有 > 10kb 的 key 输出到一个 csv 文件

rdb dump.rdb -c memory --bytes 10240 -f live_redis.csv

6.5 代码埋点

如果是代码写入Redis,例如在Java代码中将数据写入到Redis里,可以直接在代码里进行埋点统计。

6.6 公有云的Redis分析服务

基于某些公有云或者公司内部架构的Redis一般都会有可视化的页面和分析工具,来帮助我们定位大key,当然页面底层也可能是基于bigkeys或者rdb文件离线分析的结果。如阿里云社区提供了一款基于Python编写的大key定位工具:https://developer.aliyun.com/article/117042


七、大key解决方式

实际上解决大key问题的核心就是减小value的占用空间,大多数方法都集中在对存量的大key进行改造,如清理过期数据等方式,除此之外,也有通用的方案,在开始存储时就预先设计好,如压缩数据及拆分存储等方式。
在这里插入图片描述

7.1 增加监控告警

通过监控系统并设置合理的Redis内存报警阈值来提醒我们此时可能有大Key正在产生,如:Redis内存使用率超过70%,Redis内存1小时内增长率超过20%等。如果是应用代码里写入数据到Redis,那么做好埋点,然后加监控告警也是非常重要的,既可以对历史数据进行监控,定位大key,又可以避免新增的逻辑代码中出现大key这一隐患。因此,埋点监控是基石。

7.2 数据结构优化

优化 redis 的数据结构,使用合适的数据结构来存储数据,避免出现 redis 大 key 的情况。例如,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息。

7.3 清理过期数据

如果某个Key有业务不断以增量方式写入大量的数据,并且忽略了其时效性,这样会导致大量的失效数据堆积。可以通过定时任务的方式,对失效数据进行清理。设置合理的过期时间。为每个key设置过期时间,并设置合理的过期时间,以便在数据失效后自动清理,避免长时间累积的大Key问题。

  • Redis 4.0及之后版本:可以通过UNLINK命令安全地删除大Key甚至特大Key,该命令能够以非阻塞的方式,逐步地清理传入的Key。 Redis UNLINK 命令类似与 DEL 命令,表示删除指定的 key,如果指定 key 不存在,命令则忽略。 UNLINK 命令不同与 DEL
    命令在于它是异步执行的,因此它不会阻塞。 UNLINK 命令是非阻塞删除,非阻塞删除简言之,就是将删除操作放到另外一个线程去处理。
  • Redis 4.0之前的版本:建议先通过SCAN命令读取部分数据,然后进行删除,避免一次性删除大量key导致Redis阻塞。 Redis Scan 命令用于迭代数据库中的数据库键。 SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标,
    用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。

启用内存淘汰策略。启用Redis的内存淘汰策略,例如LRU(Least Recently Used,最近最少使用),以便在内存不足时自动淘汰最近最少使用的数据,防止大Key长时间占用内存。

7.4 压缩数据

在向Redis中写数据时,可以使用序列化、压缩算法将key的大小控制在合理范围内,但是需要注意序列化、反序列化都会带来一定的消耗。

Redis 支持多种压缩算法,例如 LZF、QUICKLZ 和 GZIP 等算法。不过经过推荐和测试,本文推荐ZSTD为压缩工具,因为相比其他主流压缩算法ZSTD提供了更优秀的压缩比。另外在使用随机字符串本地循环测试后,发现处理较小规模的随机字符串时,ZSTD压缩效果并不明显,如下表格所示,对于0.1KB的数据压缩后的文件大小甚至会有所增加。这可能是由于小数据量的特点导致压缩算法无法充分发挥其优势,也有可能是该压缩算法不适用于小数据量压缩。因此,为了避免浪费无谓压缩带来的CPU损耗和耗时,可以引入阈值判断机制,只有当数据大小超过10KB时才触发ZSTD压缩操作。

压缩前(KB)压缩后(KB)压缩率
0.010.022-89%
0.10.122-9%
10.78421%
107.4725%
10074.625%
100074625%

7.5 拆分存储

压缩技术确实可以有效减小value的占用空间,从而解决大部分key问题,但是如果压缩后,value还是很大,那么可以进一步对key进行拆分。key拆分的具体思路是:将原先的单个大key拆分成一个主key和多个子key,其中主key主要保存子key之间的关联信息,但不包含具体的值,而子key负责存储拆分过后的值信息。通过拆分大幅降低单个key的value大小,从而有效解决原有的大key问题。

往Redis中写的拆分方案如下图所示:
在这里插入图片描述
具体步骤:
首先,假设有个key 为key:user,然后需要判断key:user对应的值的size是否超过指定阈值,例如10KB,如果size小于10KB,则之间将其值写入到Redis中;如果size小大于10KB,则需要将其值按照一定方式进行分解,生成一个key和多个子key。在拆分完key之后,就需要保证主key和子key列表能够批量原子写入到Redis中,这一操作非常重要,如果不能保证其原子性就可能会出现部分key写入失败的情况,进而导致并发读取数据的不一致问题。

从Redis中读取的操作其实就是写入的逆操作,其主要就是将生成的主key和子key重新合并起来,方案如下图所示:
在这里插入图片描述

具体步骤:
首先,根据key:user从Redis中获取到子key列表,根据子key列表再利用mget命令从Redis中获取子key的值,然后按一定方式进行合并还原,最终校验length。

7.5.1 按时间/业务拆分

如果 Big Key 包含的是按时间顺序排列的数据,可以考虑按时间范围拆分一下,但是拆分后的每个key对应的值应该进行二次校验,确认其大小是否低于大key的阈值判断标准。

7.5.2 按哈希拆分

如果要存储的数据不是按时间顺序排列的数据,那么可以考虑哈希的方式进行拆分。而这一步首先我们需要先确定拆分的子key的数量:可以通过size(value) / 10KB计算出子key数量,然后每次存取的时候,先在本地计算field的hash值,模除 10000, 就确定了该field落在哪个key上。


八、总结

大key和大value的危害是一致的:内存不均、阻塞请求、阻塞网络等。本文主要详细介绍了大value产生的原因、影响、检测方法和解决方案。通过优化数据结构设计、压缩存储、拆分存储等方法,我们可以有效地解决和预防大key问题,从而提高Redis系统的稳定性和性能。


创作不易,如果有帮助到你的话请给点个赞吧!我是Wasteland,下期文章再见!

在这里插入图片描述


http://www.kler.cn/a/507446.html

相关文章:

  • 【算法学习笔记】30:埃氏筛(Sieve of Eratosthenes)和线性筛(Linear Sieve)
  • eBay账号安全攻略:巧妙应对风险
  • VD:生成a2l文件
  • 【java】java入门
  • [操作系统] 深入理解操作系统的概念及定位
  • [Qualcomm]Qualcomm MDM9607 SDK代码下载操作说明
  • WordPress内容保护策略:如何反击并利用被盗内容
  • MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 基础篇 part 5
  • NVIDIA发布个人超算利器project digital,标志着ai元年的开启
  • BERT的中文问答系统64
  • docekr在 x86d的 环境下打包arm64架构的docker包
  • RK3568平台(音频篇)lineout无声调试
  • 游程编码RLE的简单解释
  • opencv基础学习
  • 画流程图 代码生成流程图 流程图自动运行
  • JavaScript系列(31)--装饰器详解
  • uc/os-II 原理及应用(八) 系统裁减以及移植到51单片机-下
  • Redis登录优化
  • YUV转RGB
  • Python编程与机器学习:解锁气象、海洋、水文领域的新实践
  • 从 0 开始实现一个 SpringBoot + Vue 项目
  • windows远程桌面连接限定ip
  • HTTP 性能优化策略
  • 【设计模式】 单例模式(单例模式哪几种实现,如何保证线程安全,反射破坏单例模式)
  • 关于ubuntu命令行连接github失败解决办法
  • 小哆啦的跳跃挑战:能否突破迷宫的极限?