避免使用 Redis bigkey
2019-04-30 / 阅读(605) /
摘要: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
- 生成内存快照
$ 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
取回来,再在业务层做合并。