Redis調(diào)優(yōu)-BigKey如何處理?
Redis大Key核心問(wèn)題
Redis庫(kù)中大數(shù)據(jù)量如何遍歷?
主機(jī)配置:
- redis: 6.2.14
- 主機(jī)內(nèi)存:8G
圖片
執(zhí)行步驟:
- 生成1000W條記錄腳本,插入redis數(shù)據(jù)庫(kù)
#!/bin/bash
# Redis服務(wù)器地址和端口
REDIS_HOST="localhost"
REDIS_PORT=6379
# 輸出文件名
OUTPUT_FILE="/tmp/redis-bigkey.txt"
# 要插入的數(shù)據(jù)條數(shù)
NUM_ENTRIES=1000000
# 清除輸出文件,如果它已存在
> "$OUTPUT_FILE"
# 生成數(shù)據(jù)并插入到Redis中,同時(shí)輸出到文件
for ((i=1; i<=$NUM_ENTRIES; i++)); do
# 生成一個(gè)隨機(jī)的key和value,這里簡(jiǎn)化處理,僅使用數(shù)字作為key和value
KEY="key$i"
VALUE="$i"
# 將key和value輸出到文件中
echo "set $KEY $VALUE" >> "$OUTPUT_FILE"
# 如果需要的話,可以在這里添加檢查來(lái)確認(rèn)SET操作是否成功
# 比如:redis-cli -h $REDIS_HOST -p $REDIS_PORT GET "$KEY" | grep -q "$VALUE"
# 如果上面的命令返回非零狀態(tài),可以記錄錯(cuò)誤或者退出腳本
done
echo "數(shù)據(jù)已插入Redis并輸出到$OUTPUT_FILE"
- 讀取命令集,插入redis數(shù)據(jù)庫(kù)
cat /tmp/redis-bigkey.txt | /usr/local/redis/redis-6.2.14/src/redis-cli -h 192.168.XXX.XXX -p 6379 -a ****** --pipe
這條命令是會(huì)將一個(gè)文本文件的內(nèi)容通過(guò)管道(pipe)發(fā)送到Redis的命令行接口并執(zhí)行。
重要參數(shù)說(shuō)明:
cat /tmp/redis-bigkey.txt:
- cat 命令用于讀取 /tmp/redis-bigkey.txt 這個(gè)文件的內(nèi)容。
-a ******:
- -a 參數(shù)用于指定連接Redis服務(wù)器所需的密碼。
- ****** 是連接Redis服務(wù)器時(shí)使用的密碼。
--pipe:
- --pipe 是一個(gè)特殊的選項(xiàng),它告訴 redis-cli 通過(guò)管道從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù),并作為Redis命令發(fā)送到服務(wù)器。
注意:這里/tmp/redis-bigkey.txt 文件包含一系列的Redis命令,這些命令將被批量執(zhí)行。例如,文件中可能包含 SET、GET、DEL 等命令,每行一個(gè)命令。使用 --pipe 選項(xiàng)時(shí),需要確保Redis服務(wù)器配置允許批量操作。
- 執(zhí)行后結(jié)果,redis數(shù)據(jù)庫(kù)中有1000W數(shù)據(jù)
127.0.0.1:6379> dbsize
(integer) 1000000
嘗試用 keys * 遍歷,耗時(shí)8.55s
圖片
??由此可見,生產(chǎn)環(huán)境的數(shù)據(jù)量可能不止這些??上氡闅v一次可能的耗時(shí)。那么,如何正確遍歷呢? 使用SCAN命令。
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
??SCAN 命令是一個(gè)基于游標(biāo)的迭代器,每次被調(diào)用之后, 都會(huì)向用戶返回一個(gè)新的游標(biāo), 用戶在下次迭代時(shí)需要使用這個(gè)新游標(biāo)作為 SCAN 命令的游標(biāo)參數(shù), 以此來(lái)延續(xù)之前的迭代過(guò)程。
簡(jiǎn)單演示
127.0.0.1:6379> scan 2 match * count 10
1) "720898"
2) 1) "key772152"
2) "key318823"
3) "key851172"
4) "key137276"
5) "key658069"
6) "key486655"
7) "key795861"
8) "key300972"
9) "key488665"
10) "key479460"
11) "key15673"
什么是大Key,多大是大Key?
注意:Redis中的大key,實(shí)際上指的是key所關(guān)聯(lián)的value值特別大,或者是某種數(shù)據(jù)結(jié)構(gòu)(如hash, set, zset, list)中存儲(chǔ)了過(guò)多的元素。
詳情可參照《阿里Redis開發(fā)規(guī)范》
圖片
一般來(lái)講,String類型控制在10KB以內(nèi),hash、list、set、zset元素個(gè)數(shù)不要超過(guò)5000。
為什么會(huì)產(chǎn)生BigKey?
大key的產(chǎn)生一般與業(yè)務(wù)方設(shè)計(jì)有關(guān),對(duì)vaule的動(dòng)態(tài)增長(zhǎng)問(wèn)題預(yù)估不足。造成大key問(wèn)題的原因有:
- 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)不合理。在不適用的場(chǎng)景下使用Redis,易造成Key的value過(guò)大,如使用String類型的Key存放大體積二進(jìn)制文件型數(shù)據(jù);
- 業(yè)務(wù)規(guī)劃設(shè)計(jì)不足。沒有對(duì)Key中的成員進(jìn)行合理的拆分將大key變成小key,從而造成個(gè)別Key中一直往value里面塞數(shù)據(jù),沒有刪除機(jī)制,未定期清理無(wú)效數(shù)據(jù),導(dǎo)致不斷增加。
- 上線前期預(yù)估不足。如頭條重大新聞,造成value值動(dòng)態(tài)突增。如:百度熱搜
圖片
- 匯總統(tǒng)計(jì)類,隨著時(shí)間推移value逐漸增加
產(chǎn)生大Key會(huì)有什么問(wèn)題?
- 內(nèi)存不足(因?yàn)閞edis基于內(nèi)存)
- 刪除超時(shí)
- 網(wǎng)絡(luò)阻塞
- 集群節(jié)點(diǎn)容量?jī)A斜甚至宕機(jī)
因此需引起足夠重視。
如何判定redis變慢了?
- Redis 基準(zhǔn)性能測(cè)試
- 測(cè)試基準(zhǔn)
??了解Redis 在生產(chǎn)環(huán)境服務(wù)器上的基準(zhǔn)性能,才能進(jìn)一步評(píng)估,當(dāng)其延遲達(dá)到什么程度時(shí),才認(rèn)為Redis確實(shí)變慢了。例如:按自身硬件配置,可能延遲是0.5ms 時(shí)就可以認(rèn)為Redis 變慢了。
- 測(cè)試方法
執(zhí)行以下命令,測(cè)試出這個(gè)實(shí)例60 秒內(nèi)的最大響應(yīng)延遲:
./redis-cli --intrinsic-latency 60
[root@bogon src]# ./redis-cli --intrinsic-latency 60
Max latency so far: 1 microseconds.
Max latency so far: 25 microseconds.
Max latency so far: 220 microseconds.
Max latency so far: 253 microseconds.
Max latency so far: 351 microseconds.
Max latency so far: 448 microseconds.
Max latency so far: 514 microseconds.
1706810010 total runs (avg latency: 0.0352 microseconds / 35.15 nanoseconds per run).
Worst run took 14622x longer than the average latency.
從輸出結(jié)果可以看到,這60 秒內(nèi)的最大響應(yīng)延遲為514 微秒(0.514 毫秒)。
還可以使用以下命令,查看一段時(shí)間內(nèi)Redis 的最小、最大、平均訪問(wèn)延遲。如下:redis-cli 每隔1秒向 Redis 服務(wù)器發(fā)送一個(gè) PING 命令,并測(cè)量其往返時(shí)間.
Redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1
[root@bogon src]# redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1
min: 0, max: 1, avg: 0.15 (82 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.06 (80 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.12 (82 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.09 (81 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.07 (82 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.07 (82 samples) -- 1.01 seconds range
根據(jù)《阿里開發(fā)手冊(cè)》如果你觀察到的Redis 運(yùn)行時(shí)延遲是其基線性能的2倍及以上,就可以認(rèn)定Redis變慢了。
- 使用Redis慢日志
Redis 提供了慢日志命令的統(tǒng)計(jì)功能,它記錄了有哪些命令在執(zhí)行時(shí)耗時(shí)比較久。
例如,設(shè)置慢日志的閾值為5毫秒,并且保留最近10條慢日志記錄:
# 命令執(zhí)行耗時(shí)超過(guò) 5 毫秒,記錄慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近 10 條慢日志
CONFIG SET slowlog-max-len 10
??如果你查詢慢日志發(fā)現(xiàn),并不是復(fù)雜度過(guò)高的命令導(dǎo)致的,而都是SET/DEL這種簡(jiǎn)單命令出現(xiàn)在慢日志中,那么你就要懷疑你的實(shí)例否寫入了bigkey。
如何發(fā)現(xiàn)BigKey?
使用命令redis-cli --bigkeys給出每種數(shù)據(jù)結(jié)構(gòu)最大的bigkey,同時(shí)給出每種數(shù)據(jù)類型的鍵值個(gè)數(shù)和平均大小。
redis-cli --bigkeys
[root@bogon 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 '"key162116"' with 6 bytes
[75.91%] Biggest string found so far '"key1000000"' with 7 bytes
[100.00%] Sampled 1000000 keys so far
-------- summary -------
Sampled 1000000 keys in the keyspace!
Total key length in bytes is 8888896 (avg len 8.89)
Biggest string found '"key1000000"' has 7 bytes
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)
1000000 strings with 5888896 bytes (100.00% of keys, avg size 5.89)
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)
注意:對(duì)線上實(shí)例進(jìn)行bigkey掃描時(shí),Redis 的OPS(Operation Per Second 每秒操作次數(shù))會(huì)突增,掃描過(guò)程最好控制一下掃描的頻率,指定-i 參數(shù),命令:redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 1.它表示掃描過(guò)程中每次掃描后休息的時(shí)間間隔,單位是秒。
但是,如果想要獲得一個(gè) key 和它的值在 RAM 中所占用的字節(jié)數(shù)。需要使用以下命令:
redis 127.0.0.1:6379> MEMORY USAGE key [SAMPLES count]
例如:
127.0.0.1:6379> MEMORY usage key1000000
(integer) 56
當(dāng)我們發(fā)現(xiàn)生產(chǎn)的大Key后,那么如何進(jìn)行刪除?
如何處理大Key?
我們按照不同數(shù)據(jù)類型,給出以下命令:
- String類型: DEL/UNLINK
刪除Redis中String類型的大Key,你可以使用DEL命令:
DEL key [key ...]
如果你使用的是Redis的集群模式,可以使用redis-cli的-c選項(xiàng)來(lái)啟用集群模式,并執(zhí)行刪除命令。
redis-cli -c DEL key_name
由于DEL命令會(huì)對(duì)Redis服務(wù)器造成阻塞,可以考慮使用UNLINK命令。Redis 4.0及以上版本中可用,它會(huì)異步地刪除Key,避免阻塞。
UNLINK key [key ...]
注意: 即使使用UNLINK命令,刪除非常大的Key仍然可能會(huì)對(duì)Redis服務(wù)器造成一些影響,因?yàn)樗匀恍枰尫艃?nèi)存。因此,在生產(chǎn)環(huán)境中執(zhí)行此類操作時(shí),請(qǐng)務(wù)必謹(jǐn)慎,并考慮在低峰時(shí)段進(jìn)行,同時(shí)監(jiān)控Redis的性能指標(biāo)。
- Hash類型:HSCAN + HDEL
HSCAN key cursor [MATCH pattern] [COUNT count]
127.0.0.1:6379> HSET myhash field1 value1
(integer) 1
127.0.0.1:6379> HSET myhash field2 value2
(integer) 1
127.0.0.1:6379> HSET myhash field3 value3
(integer) 1
127.0.0.1:6379> HSCAN myhash 0 MATCH * COUNT 10
1) "0"
2) 1) "field1"
2) "value1"
3) "field2"
4) "value2"
5) "field3"
6) "value3"
127.0.0.1:6379> HDEL myhash field2
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field1"
2) "value1"
3) "field3"
4) "value3"
- List類型:LTRIM漸進(jìn)式刪除
LTRIM key start stop
redis> RPUSH mylist "one"
(integer) 1
redis> RPUSH mylist "two"
(integer) 2
redis> RPUSH mylist "three"
(integer) 3
redis> LTRIM mylist 1 -1
"OK"
redis> LRANGE mylist 0 -1
1) "two"
2) "three"
redis>
- Set類型:使用sscan每次獲取部分元素,再使用srem命令刪除每個(gè)元素
127.0.0.1:6379> SADD myset e1 e2 e3
(integer) 0
127.0.0.1:6379> SSCAN myset 1
1) "0"
2) 1) "e3"
127.0.0.1:6379> SMEMBERS myset
1) "e2"
2) "e1"
3) "e3"
127.0.0.1:6379> SREM myset e2
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "e1"
2) "e3"
127.0.0.1:6379>
- Zset類型: 使用zscan每次獲取部分元素,再使用ZREM命令刪除每個(gè)元素
127.0.0.1:6379> zadd score 98 xm 99 xb 100 xh
(integer) 3
127.0.0.1:6379> zscan score 0
1) "0"
2) 1) "xm"
2) "98"
3) "xb"
4) "99"
5) "xh"
6) "100"
127.0.0.1:6379> ZRANGE score 0 -1 WITHSCORES
1) "xm"
2) "98"
3) "xb"
4) "99"
5) "xh"
6) "100"
127.0.0.1:6379> ZREM score xm
(integer) 1
127.0.0.1:6379> ZRANGE score 0 -1 WITHSCORES
1) "xb"
2) "99"
3) "xh"
4) "100"
127.0.0.1:6379>
生產(chǎn)BigKey如何調(diào)優(yōu)?
??采用惰性刪除策略。具體在${redis_home}/redis.conf 文件配置修改
lazyfree-lazy-server-del yes
replica-lazy-flush yes
lazyfree-lazy-user-del yes
總結(jié)
Redis中的大Key指的是占用內(nèi)存特別大的Key,處理不當(dāng)可能導(dǎo)致性能下降、內(nèi)存消耗大等問(wèn)題。
解決方案:
- 避免創(chuàng)建大Key:設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)時(shí),盡量分散數(shù)據(jù),避免單一Key過(guò)大。
- 分批次處理:對(duì)于已存在的大Key,使用相關(guān)命令(如SCAN)分批次讀取和刪除。
- 設(shè)置過(guò)期時(shí)間:為大Key設(shè)置TTL,讓Redis自動(dòng)清理。
- 監(jiān)控與告警:使用監(jiān)控工具及時(shí)發(fā)現(xiàn)大Key,并設(shè)置告警通知。
- 優(yōu)化網(wǎng)絡(luò):如果刪除大Key時(shí)網(wǎng)絡(luò)壓力大,考慮增加帶寬或優(yōu)化網(wǎng)絡(luò)連接。
注意事項(xiàng):
- 處理大Key時(shí)要謹(jǐn)慎,最好在低峰時(shí)段操作。