• Redis中的BigKey如何发现和处理



    579a429daf314744b995f37351b46548

    什么是BigKey?

    通常来说,如果一个键值对大于一定阈值(例如 1MB),它就可以被认为是一个大键或 bigkey。

    大键的存在通常被认为是不好的,主要原因:

    ​ 1.占用大量内存,可能导致内存不足
    ​ 2.增加内存碎片,降低内存利用效率
    ​ 3.增加SAVE和复制时间
    ​ 4.增加AOF重写时间
    ​ 5.压缩列表元素过多,降低查找效率

    所以在使用 Redis 时,应该尽量避免 bigkey 的产生。

    常见的bigkey原因:

    ​ 1.一个 Hash 类型键值对字段过多
    ​ 2.一个 Set 类型包含了过多的成员
    ​ 3.一个 List 类型包含了过多的元素
    ​ 4.一个 Bitmaps 类型映射了过大的内容
    ​ 5.一个 Zset 类型包含了过多的成员

    一般建议 bigkey 的大小控制在 1MB 以内为宜。


    BigKey危害?

    Redis 中存在的BigKey会带来以下几个方面的危害:

    image-20231014225057908

    占用大量内存空间

    BigKey由于值过于庞大,会占用Redis sehr大比例的内存,从而对Redis的内存使用造成浪费和压力。

    阻塞服务器进程

    当需要对BigKey进行操作如删除、更改值时,这些操作需要花费较长时间,会造成Redis主进程阻塞,影响Redis的正常工作。

    加长持久化时间

    Redis需要进行持久化将数据写入磁盘保存,BigKey会因其数据量大而导致持久化过程非常缓慢。

    延长复制时间

    当Redis进行主从复制时,同步BigKey也会非常慢,可能导致从服务器的数据同步效率下降。

    增加内存碎片

    BigKey的大值会导致Redis内存中产生大量不连续的碎片,降低内存利用效率。

    加重AOF重写压力

    BigKey会导致AOF文件过大,当Redis进行AOF重写时,需要处理大量数据,加重服务器压力。

    降低查找效率

    对于一些集合性的数据结构如Hash、List等,BigKey中的数据量庞大,会降低查找效率。
    所以,对于Redis来说,发现并解决BigKey问题是非常重要的。

    如何发现BigKey?

    在Redis中,可以通过以下几种方式发现BigKey:

    image-20231014225207261

    info命令

    使用Redis的info命令,查看keyspace部分,观察biggest_key_size的大小,当biggest_key_size过大时,说明存在BigKey。

    可以通过analyze命令的keyspace部分来观察biggest_key_size,判断是否存在BigKey。例如:

    127.0.0.1:6379> info keyspace
    # Keyspace
    db0:keys=5,expires=0,avg_ttl=0
    db1:keys=8,expires=0,avg_ttl=0
    db2:keys=4,expires=0,avg_ttl=0
    db3:keys=5,expires=0,avg_ttl=0
    db4:keys=4,expires=0,avg_ttl=0
    db5:keys=5,expires=0,avg_ttl=0
    db6:keys=5,expires=0,avg_ttl=0
    db7:keys=4,expires=0,avg_ttl=0
    db8:keys=4,expires=0,avg_ttl=0
    db9:keys=4,expires=0,avg_ttl=0
    db10:keys=5,expires=0,avg_ttl=0
    db11:keys=5,expires=0,avg_ttl=0
    db12:keys=5,expires=0,avg_ttl=0
    db13:keys=4,expires=0,avg_ttl=0
    db14:keys=4,expires=0,avg_ttl=0
    db15:keys=4,expires=0,avg_ttl=0
    
    # ... 略
    
    biggest_key_size:8192
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    从上面的biggest_key_size大小可以看出,目前存在value超过8KB的大键。

    scan命令

    使用scan命令迭代Redis中的键,同时利用Redis的debug object命令查看key对应的value大小,如果超过阈值即判定为BigKey。

    可以通过SCAN命令迭代Redis中的键,并配合DEBUG OBJECT命令判断键值大小,来发现BigKey。例如:

    127.0.0.1:6379> scan 0 match * count 10
    1) "17"
    2)  1) "key:1"
        2) "key:2"
        3) "key:3"
        4) "key:4" 
        5) "bigkey"
    
    127.0.0.1:6379> debug object bigkey
    Value at:0x7fdafb0258e0 refcount:1 encoding:raw serializedlength:1001927
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面先通过SCAN命令扫描出键,然后使用DEBUG OBJECT判断bigkey的值大小,结果显示serializedlength超过1MB。所以这是一个BigKey。我们可以编写一个循环使用SCAN的程序,设置一个大小阈值,所有值超过阈值的键都记录下来,从而扫描出所有BigKey。相比直接遍历键空间,SCAN实现渐进式扫描,可以避免命令阻塞,更安全的发现BigKey。

    Redis-cli

    使用redis-cli工具的–bigkeys参数,该参数会扫描Redis中的键并返回所有大小超过指定阈值的BigKey。

    第三方工具

    使用一些第三方Redis可视化管理工具,如Redis Enterprise、Astra等,这些工具提供了BigKey检测功能。

    日志监控

    分析Redis日志,关注slave缓慢或者持久化被block的情况,这可能与BigKey有关。

    定期主动扫描

    可以通过脚本等定期主动扫描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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    主要步骤包括:

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

    你可以将它部署为持续运行的任务,自动发现Redis中的大键。

    综合利用上述各种方式,可以有效发现Redis中存在的BigKey,作为后续优化的依据。


    如何删除BigKey?

    在Redis中删除BigKey可以通过以下几种方法:

    DEL命令

    直接使用DEL命令删除键即可快速删除单个BigKey,但是这会导致数据丢失。

    DEL big_key
    
    • 1
    重新设计键

    如果BigKey是由于键设计不当导致的,那么可以重新设计键结构,拆分BigKey。
    例如将原来的一个Hash BigKey拆分为多个Hash小Key。

    使用UNLINK

    UNLINK可以异步删除键,不会堵塞服务器。但也存在数据丢失风险。

    UNLINK big_key 
    
    • 1
    配合事务操作

    可以先用MULTI开启事务,再使用GET、DEL等命令处理BigKey,最后用EXEC提交事务。这样可以避免阻塞服务器。

    分段删除

    对于List、Hash等结构的BigKey,可以通过区间查找、分段删除的方式进行分批删除,避免一次性删除耗时太长。

    使用SCAN命令

    可以通过SCAN命令渐进式地扫描并删除BigKey。

    热重启

    可以考虑停止Redis服务,并删除持久化文件(rdb、aof),然后重启,这样可以直接清除所有BigKey,但会损失全部数据。

    可以根据实际情况选择最佳方案删除BigKey。

    Hash类型Bigkey如何处理?

    对于Redis中的Hash类型Bigkey,可以通过以下几种方式进行优化:

    拆分Bigkey

    最直接的方法是将一个大的Hash Key拆分为多个小的Hash Key,防止单个键值对过大。例如

    将user:{uid} 拆分为 user:{uid}:info、user:{uid}:data等。
    
    • 1
    使用Hash数据结构

    Redis的Hash相比字符串可以大幅度减少内存使用。可以考虑将字符串值转换为Hash结构,字段由字符串改为Hash。

    海量计数器转换为Bitmaps

    如果Hash中的字段是简单的计数器,可以考虑用Bitmaps替代,由于Bitmaps有压缩特性,可以减少内存使用。

    采用更紧凑的编码

    对于Hash的值,如果是数字,可以选择更紧凑的编码方式,如整形float编码转为整数int编码。

    控制字段的数量

    对Hash字段数量进行限制,避免单个Hash过多字段,导致结构过大。

    使用内存优化参数

    调整Redis的ziplist、hash-max-ziplist-value等参数,优化内存使用。

    redis>config get hash-max-ziplist-entries
    1) "hash-max-ziplist-entries"
    2) "512"  --默认原来是512
    
    redis>config set hash-max-ziplist-entries 1000
    "OK"
    redis>config get hash-max-ziplist-entries
    1) "hash-max-ziplist-entries"
    2) "1000" --调整为1000,但是不建议调整超过1000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    海量小值分离

    如果Hash中存在大量小值字段,可以将这些小值字段拆分出去,单独用一个Hash Key存储。

    假设我们有一个 user:{uid} 的 hash 类型 bigkey,里面存储了用户的名称、年龄、手机号等信息。其中手机号字段的值都是数字且较小。
    我们可以这样进行海量小值的分离:

    1. 为手机号创建新的 hash key

      user:{uid}:phone
      
      • 1
    2. 遍历 user:{uid} 的 hash,提取所有手机号字段和值,存入 user:{uid}:phone

      HGETALL user:{uid} # 遍历获取所有字段
      ...
      HSET user:{uid}:phone {phone_field} {phone_value} # 存储到新key
      
      • 1
      • 2
      • 3
    3. 从 user:{uid} 删除这些手机号字段

      HDEL user:{uid} {phone_field_1} {phone_field_2} ... # 删除手机号字段
      
      • 1

    这样我们就将海量的手机号小值从大key中分离出来,单独用一个 hash 存储。

    分离后的优点:

    1.原来的 user:{uid} 键值对会缩小,不再存在大量小值

    2.由于是小值,在新 key 中可以使用更紧凑的内存编码存储

    3.查找手机号等小值时,只需要操作 user:{uid}:phone 即可,避免遍历全量大key

    综合使用这些优化策略,可以有效减小Hash类型Bigkey的内存占用。


    写在最后

    感谢您的支持和鼓励! 😊🙏

    如果大家对相关文章感兴趣,可以关注公众号"架构殿堂",会持续更新AIGC,java基础面试题, netty, spring boot, spring cloud等系列文章,一系列干货随时送达!

  • 相关阅读:
    对工作还有Bar Raiser的一些感想
    力扣-461.汉明距离
    【美团秋招笔试】美团第一次笔试 2022-8-20
    Jmeter 的使用
    5分钟带你了解RabbitMQ的(普通/镜像)集群
    Cilium系列-1-Cilium特色 功能及适用场景
    【C++】静态库.a和动态库.so文件的生成和使用
    MATLAB命令
    Leetcode 386. 字典序排数
    Django重定向类HttpResponseRedirect、HttpResponsePermanentRedirect和重定向函数redirect
  • 原文地址:https://blog.csdn.net/jinxinxin1314/article/details/133837874