避免使用 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
取回來,再在業務層做合並。