Redis Bigkey or Hotkey issue?

說明什麼是Bigkey issue? 什麼是 Hotkey issue? 排查方向、及解決建議。

Jerry’s Notes
What’s next?
7 min readMar 20, 2022

--

Bigkey issue- 什麼是bigkey?

在Redis中,一個字符串最大512MB,一個二級數據結構(例如hash、list、set、zset)可以存儲大約40億個(2³²-1)個元素,但實際上中如果下面幾種情況,我就會認為它是bigkey。

一個STRING類型的Key,它的值為5MB(數據本身過大,這是相對的,如果大部份的數據都是10KiB,而出現一個 5MB的鍵值,那麼這個鍵值,就是大鍵值Big-key)。
一個LIST類型的Key,它的列表數量為20000個(列表數量過多)
一個ZSET類型的Key,它的成員數量為10000個(成員數量過多)
一個HASH格式的Key,它的成員數量雖然只有1000個但這些成員的value總大小為100MB(成員體積過大)

!!! 上面這些條件,還要比較實際的使用情境,沒有絕對值。

Bigkey 會造成的問題?

內存空間不均勻(多分片組)。在集群模式中,由于bigkey的存在,会造成主机节点的内存不均匀,这样会不利于集群对内存的统一管理,存在丢失数据的隐患。造成單節點記憶體用很多,其他很少,例如特定分片組內存使用量遠高於其他分片組。

scaling-out/in 時會造成 slot 分佈不平圴。
操作延遲。由於Redis單線程的特性,操作bigkey的通常比較耗時,也就意味著阻塞Redis可能性越大,這樣會造成客戶端阻塞或者引起故障切換,它們通常會出現在慢查詢中。
網絡擁塞。bigkey也就意味著每次獲取要產生的網絡流量較大,假設一個bigkey為1MB,客戶端每秒訪問量為1000次,那麼每秒產生1000MB的流量。
bigkey過期刪除時造成命令阻塞。 若bigkey設置了過期時間,當它過期後,會被刪除,如果沒有使用Redis 4.0的過期異步刪除(lazyfree-lazy-expire yes),就會存在阻塞Redis的可能性,而且這個過期刪除不會從主節點的慢查詢發現。
刪除一個大Key造成主庫較長時間的阻塞,並引發同步中斷或主從切換。(改善方式及理由同上。更新版本至 4.0.10 以上。)

Resharding and shard rebalancing failed,slot中有大於 256MB的鍵值,該slot不會搬。(多分片組時 — shards)。

!!! 簡單來說,若客戶存取少,全部都是bigkey也沒差,但大量使用,即使 只有100KiB 大小,也會是大問題。

怎麼產生的?

一般來說,bigkey的產生都是由於程序設計不當。沒有對Key中的成員進行合理的拆分,造成個別Key中的成員數量過多(大Key)。

工具及排查方式

— bigkeys會重覆去讀endpoint, 如果用全域的可能會連到不同節點. (https://redis.io/topics/rediscli

Q: 要指定特定節點來跑嗎? 
是的,要。只對特定 shard 來分析。
$ redis-cli xxxx --bigkeysInvalid reply type (6) for TYPE on key 'h5:5aec7ad4e67f55bc254bd20ee89c2242'!!! 解法: 指定特定節點來跑。不要用 cluster mode enable 的 endpoint 來跑。https://redis.io/docs/manual/cli/

Scanning for big keys
In this special mode, redis-cli works as a key space analyzer. It scans the dataset for big keys, but also provides information about the data types that the data set consists of. This mode is enabled with the --bigkeys option, and produces verbose output:
# --bigkeys 是利用 scan 命令來掃描鍵值。
$ redis-cli --bigkeys
# 使用 -i 0.1, 讓指令每隔100條scan指令就休眠0.1s。
$ redis-cli --bigkeys -i 0.1

執行 ”redis-cli — bigkeys” 時預設每次是掃描小量的key,並且重覆執行掃描的動作,直到所有的數據都遍歷過,因此在執行的過程中,會增加redis的負載,故建議在副本節點上執行。

•主動方式: 使用 scan v 2.8.0 (https://redis.io/commands/scan) + debug object 去掃鍵值來分析。

• key 在那個 slot? <== 可以用命令來確認

https://redis.io/commands/cluster-keyslot
> CLUSTER keyslot keyName
https://redis.io/commands/cluster-keyslot
> CLUSTER keyslot {aaa}KeyName

BigKeys行為說明 — Forkless backup behavior on big size keys

The basic idea of a backup strategy is to dump all the data present in Redis i.e memory to disk. Now internally this requires mapping the data format from Memory to the rdb file on disk which Redis understands. Now for forkless backup strategy there is a known issue where in if there are big keys then this causes high CPU utilization for formatting data from memory to disk, as a result of which the single thread of the Redis get busy and even the replication is impacted causing Replication lag and then a partial sync and full sync. In your case exactly the same happened. Forkless backup started on the REPLICA node; the backup started dumping large keys to disk causing high CPU utilization as this was being carried out in the same Redis thread. Due to high CPU utilization on the Replica node, the replication was impacted and replication lag increased where in first a partial sync was tried and then a full sync causing the MASTER to dump all the data to REPLICA node hence causing high CPU on MASTER node again.

如何監控?

bigkey的大操作,通常會引起 ”客戶端輸入或者輸出緩衝區” 的異常,Redis提供了info clients裡面包含的客戶端輸入緩衝區的字節數以及輸出緩衝區的隊列長度。

$ redis-cli client list
id=3 addr=127.0.0.1:58500 fd=8 name= age=3978 idle=25 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=26263554 events=r cmd=hgetall

如何解決?

•對大Key進行拆分,在業務面去將鍵值拆分。

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

•對大Key進行清理,定期刪除並設定TTL。

Update to Redis 4.0.10 以上減少bigkey刪除造成的問題。

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

•對失效數據進行定期清理,定期刪除並設定TTL。

Update to Redis 6.x 有效去主動清除過期的key。

分析 bigke 的進一步建議

一般我們會使用 redis 的 memory usage [1]命令來確認每個 string/set/hash 的數據大小, 但該命令僅在 redis 4.0 以上版本有用,所以我建議您可以使用如 redis-rdb-tools 的第三方工具[2]來確認內存的使用情形. 您可以導出您redis的備份[3]後使用該工具來進行分析, 該工具可以透過分析備份文件(rdb)來產出內存用量的報表.
[1]
https://redis.io/commands/memory-usage
[2]
https://github.com/sripathikrishnan/redis-rdb-tools#generate-memory-report
[3]
https://docs.aws.amazon.com/zh_cn/AmazonElastiCache/latest/red-ug/backups-exporting.html

使用 “./src/redis-cli” + 參數 “ — bigkeys” 來做大鍵值的分析。

$ ./src/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"' with 3 bytes

-------- summary -------
Sampled 1 keys in the keyspace!
Total key length in bytes is 3 (avg len 3.00)
Biggest string found '"key"' has 3 bytes

1 strings with 3 bytes (100.00% of keys, avg size 3.00)
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

也可以使用 “./src/redis-cli” 使用 SCAN 的方式,來分析您需要的鍵值

[+] SCAN | Redis:
https://redis.io/commands/scan/
---
SCAN cursor [MATCH pattern] [COUNT count]
---

$ ./src/redis-cli scan 0 match key* count 1000
1) "0"
2) 1) "key"

Redis RDB 分析工具 rdbtools 说明

redis-rdb-tools
https://github.com/sripathikrishnan/redis-rdb-tools
Parse Redis dump.rdb files, Analyze Memory, and Export Data to JSON

!!! 可以進一步離線來分析 rdb 檔內,所有鍵值的狀況。詳細分析使用。

$ rdb --command justkeyvals --key "user.*" /var/redis/6379/dump.rdb

user003 fname Ron,sname Bumquist,
user001 fname Raoul,sname Duke,
user002 fname Gonzo,sname Dr,
user_list user003,user002,user001

!!!請注意!!

目 redis-rdb-tools 對 Redis 7 版本的支援度可能有問題,建議改用其他方式。

# /usr/local/bin/rdb --command json r707-single-0908-0001.rdb
Traceback (most recent call last):
File "/usr/local/bin/rdb", line 11, in <module>
load_entry_point('rdbtools==0.1.15', 'console_scripts', 'rdb')()
File "/usr/local/lib/python3.6/site-packages/rdbtools/cli/rdb.py", line 106, in main
parser.parse(options.dump_file[0])
File "/usr/local/lib/python3.6/site-packages/rdbtools/parser.py", line 394, in parse
self.parse_fd(open(filename, "rb"))
File "/usr/local/lib/python3.6/site-packages/rdbtools/parser.py", line 399, in parse_fd
self.verify_version(f.read(4))
File "/usr/local/lib/python3.6/site-packages/rdbtools/parser.py", line 963, in verify_version
raise Exception('verify_version', 'Invalid RDB version number %d' % version)
Exception: ('verify_version', 'Invalid RDB version number 10') <-----

看起來是一個已知的問題的。

[+] Redis 7 Support · Issue #185 · sripathikrishnan/redis-rdb-tools · GitHub:
https://github.com/sripathikrishnan/redis-rdb-tools/issues/185

[+] Redis 7 support by Nanciico · Pull Request #193 · sripathikrishnan/redis-rdb-tools · GitHub:
https://github.com/sripathikrishnan/redis-rdb-tools/pull/193

Hotkey issue - 什麼是Hotkey?

Issue: Frequently queried keys in Redis are hotkeys, and it can cause high requests resulting in bottleneck issue on specific nodes.

所謂的熱 Key 問題都是由於對某個 Key 的訪問量過大所產生的一些衍生問題。由於某個 Key 的數據一定是存儲到後端某台服務器的 Redis 單個實例上,如果對這個 Key 突然出現大量的請求操作,這樣就會造成流量過於集中,達到 Redis 單個實例處理上限,可能會導致 Redis 實例 CPU 使用率 100%,或者是網卡流量達到上限等,對系統的穩定性和可用性造成影響,或者更為嚴重出現服務器宕機,無法對外提供服務;更有甚者在出現 Redis 服務不可用之後,大量的數據請求全部落地數據庫查詢上,Redis 都已經頂不住了,數據庫也是分分鐘掛掉的節奏,這個是所謂的熱 Key 問題。

怎麼產生的?

1) 業務邏輯上,造成對特定 key 值 / slot 做大量存取的行為。

2) 使用 hash key 去大量存取特定 slot 的鍵值

hotkey造成的問題有那些?

特定 shard / 特定節點特別忙碌。

•特定的 node 會特別忙

•節點效能上限。流量集中,达到服务器处理上限 (CPU、网络 IO….等)。

•操作延遲。由於Redis單線程的特性,操作会影响在同一个 Redis 实例上其他 Key 的读写请求操作;

•操作延遲。大量 Redis 请求失败,查询操作可能打到数据库,拖垮数据库,导致整个服务不可用。

Q: 如何發現 hotkey?

Hotkeys 因為是即時當下被存取的狀況,除了客戶端另外去記錄每一個key存取的次敗,否則沒有比較好的方式。*
實際上是通過 scan + object freq 來掃描所有的key的存取次數,由於需要掃描整個 keyspace,實時性上比較差,如果 key 的數量比較多,耗時可能非常長(實測:570M 數據,耗時5分30秒)*

$ redis-cli --hotkeys -h <redis-cluster> -p 6379
-------- summary -------
Sampled 382562 keys in the keyspace!
hot key found with counter: 231 keyname: user:125
.......

Q: To analyze what the hotkeys are, we can use.

Using redis-cli with — hotkey (https://redis.io/topics/rediscli)

> redis-cli xxxx -hotkey請注意!!
--hotkeys 是在 Redis 在 4.0 之後的版本,才支援的功能,而參數組中最大內存策略(maxmemory-policy),必需為 olatile-lfu 或是 allkeys-lfu。
於而 Redis 在 4.0 之後的版本,--hotkeys 是透過掃描鍵值的方式,透過 OBJECT 指令,來檢查特定鍵值存取頻繁的狀況,進而產生hotkeys的報表。而獲取某個key的訪問頻繁的次數,您也可以透過 OBJECT 指令來達到這個目的,但使 --hotkeys 及 Redis OBJECT 指令前,您的 Redis 集群的內存淘汰策略(maxmemory policy),必需為 LFU (volatile-lfu 或 allkeys-lfu)淘汰策略,詳細請參考以下官方文檔說明。[+] Redis OBJECT :
https://redis.io/commands/OBJECT
---
OBJECT FREQ <key> returns the logarithmic access frequency counter of the object stored at the specified key. This subcommand is available when maxmemory-policy is set to an LFU policy.
---

• Starting with 4.0.10, hotkeys option is allowed if maxmemory-policy is LFU (allkeys-lfu, volatile-lfu) related.

hotkeys則使用了key上面的Least Frequently Used屬性,這個只有4.0之後才有。(https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/ParameterGroups.Redis.html )

• OBJECT (https://redis.io/commands/OBJECT ) FREQ returns the logarithmic access frequency counter of the object stored at the specified key. This subcommand is available when maxmemory-policy is set to an LFU policy.

•實際上是通過 scan + object freq 來掃描所有的key的存取次數,由於需要掃描整個 keyspace,實時性上比較差,如果 key 的數量比較多,耗時可能非常長

• Identify the hash slot for the hotkeys: CLUSTER KEYSLOT <keyname> (https://redis.io/commands/cluster-keyslot )

Using monitor command with tools to real-time analyzer data by redis-faina. 使用 redis-faina 搭配 monitor 去即時分析(https://github.com/facebookarchive/redis-faina)

$ redis-cli -p 6490 MONITOR | head -n <NUMBER OF LINES TO ANALYZE> | ./redis-faina.py [options]

!!! 小心 !!!
monitor 命令有機會造成 output buffer increase,所以只能短時間使用。

Hokey解決方案?

Q. How to mitigate ‘hotkey’ issue?

1. 增加分片組的方式,來增加讀寫的能力。online resharding feature to scale out the cluster to increase the read/write capacity.

2. 重建offline resharding 時,指定特定 hotkey所在的 slot 在特定的 shard node上。offline resharding feature to re-create a new cluster with custom key slots distribution.

3. 使用哈希標籤( https://redis.io/topics/cluster-spec#keys-hash-tags )將熱鍵分隔為不同的分片。複製熱鍵,並讓它們在不同的分片中分發。using hashtags to force certain keys to be stored in different slot/node.

4. 使用只讀副本共享讀取工作負載。Using replica node to reduce read loading.

5. 使用本地緩存。可以利用 Redis 6 中的客戶端緩存功能( https://redis.io/topics/client-side-caching )

6. 設計多層緩存架構並與其他 AWS 緩存解決方案合作( https://aws.amazon.com/caching/aws-caching/ )。

7. key+index: 將熱鍵值複制多份

使用 redis-faina 搭配 monitor 去即時分析

https://github.com/facebookarchive/redis-faina

$ redis-cli -p 6490 MONITOR | head -n <NUMBER OF LINES TO ANALYZE> | ./redis-faina.py [options]

!!! 小心 !!!
monitor 命令有機會造成 output buffer increase,所以只能短時間使用。

Q: 為什麼個別分片組的內存用量不一致?

A: 常見的原因是有使用 hash tag,或是有大鍵值(bigkey),所以造成特定分片組的內存使用量較高(內存用量不一致)

[+] Redis 哈希(Hash):
https://www.runoob.com/redis/redis-hashes.html
https://redis.io/commands/hset
https://redis.io/commands/HGETALL

--

--

Jerry’s Notes
What’s next?

An cloud support engineer focus on troubleshooting with customer reported issue ,and cloud solution architecture.