摘要:Redis bigkey 即数据量大的 Key,比如字符串Value值非常大,哈希、列表、集合、有序集合元素多等。由于其数据大小远大于其他Key,容易造成内存不均、超时阻塞、网络流量拥塞等一系列问题。

Redis Bigkey 的危害

内存不均

导致集群内不同节点内存分布不均,间接导致访问请求倾斜,同时不利于集群统一管理,存在丢失数据的隐患。

超时阻塞

由于 Redis 单线程模型,在操作bigkey 的时候很容易出现阻塞甚至导致 Sentinel 主从切换。 常见的操作包括SMEMBERSHGETALLDEL或自动过期bigKey,一般都会出现在 Redis 慢查询日志中。

网络流量拥塞

如果 bigkey 正好又是hot key,则容易产生流量拥塞问题,比如 bigkey 为 1MB,每秒访问几千次, 对于普通千兆网卡(最大128MB/s)服务器来说是灭顶之灾,即便对于万兆网卡服务器来说也是很大压力。

而且一般服务器都会采用单机多 Redis 实例的方式部署,也就是说某个 Redis 实例上的 bigkey 可能会对其他 Redis 实例造成巨大影响。

如何发现Bigkey

方案1:redis-cli --bigkeys

使用官方的redis-cli --bigkeys时,它会对 Redis 中的 key 进行SCAN采样,寻找较大的 keys,不用担心会阻塞 Redis。

执行的结果可以用于分析 Redis 的内存的使用用状态和各种类型 key 的平均大小。

$ 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 'key-419' with 3 bytes
[05.14%] Biggest list   found so far 'mylist' with 100004 items
[35.77%] Biggest string found so far 'counter:__rand_int__' with 6 bytes
[73.91%] Biggest hash   found so far 'myobject' with 3 fields

-------- summary -------

Sampled 506 keys in the keyspace!
Total key length in bytes is 3452 (avg len 6.82)

Biggest string found 'counter:__rand_int__' has 6 bytes
Biggest   list found 'mylist' has 100004 items
Biggest   hash found 'myobject' 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)

方案2:redis-rdb-tools工具

redis-rdb-tools是用 Python 写的用来分析 Redis 的 rdb 快照文件用的工具, 它可以把 rdb 快照文件生成 CSV 或 JSON 文件,也可以导入到 MySQL 生成报表来分析。

可以通过 Python 的 pip 来安装:pip install rdbtools

使用方法:

  1. 对 slave 进行 bgsave 得到 rdb 文件
    127.0.0.1:6379> bgsave
    
  2. 生成内存快照
    $ 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的长度
  3. 将 CSV 文件导入到 MySQL 进行分析 在 MySQL 中新建表,然后导入 CSV 数据。
    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`)
     );
    
  4. 查询内存占用最高的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)
    
  5. 查询元素最多的3个 key
    mysql> SELECT * FROM memory ORDER BY num_elements 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)
    

方案对比

  • 方案1 是Redis自带工具,可以快速的扫描出各种数据类型最大的 key。
  • 方案2 是第三方工具,可以支持灵活的数据查询,可以进行全面分析。

如何删除bigkey

如果直接DELbigkey 操作可能会引发 Redis 阻塞甚至是发生 Sentinel 主从切换,那么如果清理这些 bigkey 呢?

答案是SCAN命令组,从 Redis 2.8 版本开始支持SCAN命令, 可以指定 count,来分多批枚举 bigkey,然后实现渐进式删除 bigkey。

Redis 4.0 新增UNLINK命令,是DEL命令的异步版本, 它将删除键的操作放在后台线程执行,从而尽可能地避免服务器阻塞。此外,Redis 4.0 中的FLUSHDBFLUSHALL 新增ASYNC选项, 带有这个选项的操作也将在后台线程进行。

删除big string

删除string类型的 bigkey,不会造成Redis阻塞,可以直接用DEL

删除big hash key

使用HSCAN命令,每次获取500个元素,再用HDEL命令结合pipeline批量删除。

参考 Python 代码实现:

def clean_hash_key(host, port, db, hash_key, batch_size=500):
    rd = redis.StrictRedis(host, port, db)
    pl = rd.pipeline()
    cursor = '0'  # 注意初始化为字符串'0'
    while cursor != 0:
        cursor, data = rd.hscan(hash_key, cursor=cursor, count=batch_size)
        for field in data:
            pl.hdel(hash_key, field)
        pl.execute()   # 如果单批操作耗时不超过0.1秒,可以适当调大batch_size

删除big set key

使用SSCAN命令,每次获取500个元素,再用SREM命令结合pipeline批量删除。

删除big list key

使用LTRIM命令,每次删除100个元素。

删除big sortedset key

使用ZREMRANGEBYRANK命令,每次删除Top 100个元素。

如何避免bigkey

主要是对 bigkey 进行拆分,拆成多个 key,然后用MGET取回来,再在业务层做合并。

扩展资料