避免使用 Redis bigkey
/ / 阅读数:5259摘要 :Redis bigkey 即数据量大的 Key,比如字符串 Value 值非常大,哈希、列表、集合、有序集合元素多等。由于其数据大小远大于其他 Key,容易造成内存不均、超时阻塞、网络流量拥塞等一系列问题。
Redis Bigkey 的危害
内存不均
导致集群内不同节点内存分布不均,间接导致访问请求倾斜,同时不利于集群统一管理,存在丢失数据的隐患。
超时阻塞
由于 Redis 单线程模型,在操作 bigkey 的时候很容易出现阻塞甚至导致 Sentinel 主从切换。
常见的操作包括SMEMBERS
、HGETALL
、DEL
或自动过期 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
。
使用方法:
- 对 slave 进行
bgsave
得到 rdb 文件127.0.0.1:6379> bgsave
- 生成内存快照 在生成的 CSV 文件中以下几列:
$ rdb -c memory dump.rdb > memory.csv
database
key 在 Redis 的 dbtype
key 类型key
key 值size_in_bytes
key 的内存大小encoding
value 的存储编码形式num_elements
key 中的 value 的个数len_largest_element
key 中的 value 的长度
- 将 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`) );
- 查询内存占用最高的 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)
- 查询元素最多的 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
如果直接DEL
bigkey 操作可能会引发 Redis 阻塞甚至是发生 Sentinel 主从切换,那么如果清理这些 bigkey 呢?
答案是 SCAN 命令组 ,从 Redis 2.8 版本开始支持SCAN
命令,
可以指定 count,来分多批枚举 bigkey,然后实现渐进式删除 bigkey。
Redis 4.0 新增 UNLINK 命令 ,是DEL
命令的异步版本,
它将删除键的操作放在后台线程执行,从而尽可能地避免服务器阻塞。此外,Redis 4.0 中的FLUSHDB
和FLUSHALL
新增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
取回来,再在业务层做合并。