Redis中對大Key進行處理方式

什么是大key
很多鐵子可能會認為大key,是這個key的值很大其實不是,而是key的value值很大一般對于下面這些我們可以稱為大key.
- String 類型值大于10KB。
- Hash、List、Set、Zset類型元素個數超過5000個。
大key會造成什么影響
這個大key主要會帶來下面這幾種影響:
- 阻塞工作線程:如果我們使用del命令刪除大key,會阻塞工作線程這樣就沒有辦法處理其他客戶端發(fā)過來的命令了。
- 內存分布不均: 集群模型在slot分片均勻情況下會出現數據和查詢傾斜的情況,部分有大key的Redis結點占用內存較多。
- 客戶端超時阻塞: Redis的工作線程只有一個,操作這個大key會比較耗時會阻塞Redis在客戶端看來就說很久很久沒有響應。
- 引發(fā)網絡阻塞: 每次獲取大key產生的網絡流量比較大,這對于網卡比較小的服務器是災難性的。
如何找到大key
1.我們可以通過redis客戶端提供的命令 redis-cli --bigkeys.來查看
[root@VM-4-17-centos redis]# 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 '"k1"' with 2 bytes [00.00%] Biggest hash found so far '"ksyi"' with 100 fields -------- summary ------- Sampled 2 keys in the keyspace! Total key length in bytes is 6 (avg len 3.00) Biggest hash found '"ksyi"' has 100 fields Biggest string found '"k1"' has 2 bytes 0 lists with 0 items (00.00% of keys, avg size 0.00) 1 hashs with 100 fields (50.00% of keys, avg size 100.00) 1 strings with 2 bytes (50.00% of keys, avg size 2.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)
注意:
- 在使用這個命令來查詢大key時,最好在從節(jié)點上執(zhí)行。如果在主節(jié)點上執(zhí)行會阻塞從節(jié)點。
- 如果沒有這個從節(jié)點,那么我們可以選擇在Redis業(yè)務壓力比較輕的時候執(zhí)行避免影響正常的業(yè)務功能;或者我們可以使用**-i選項**來控制掃描間隔避免長時間掃描降低Redis的性能。
這個方式其實也有缺點,他之只能返回每種類型最大的那個bigkey,無法獲得大小在前N位的bigkey。
2.使用memory usage命令進行查詢
- MEMORY USAGE命令給出一個key和它的值在RAM中所占用的字節(jié)數。返回的結果是key的值以及為管理該key分配的內存總字節(jié)數。
- 對于嵌套數據類型,可以使用選項SAMPLES,其中count表示抽樣的元素個數,默認值為5。當需要抽樣所有元素時,使用SAMPLES 0 。
其語法如下:
MEMORY USAGE key [SAMPLES count]
下面我們來演示一下
127.0.0.1:6379> memory usage ksyi (integer) 496
3.通過scan命令來查找大key
- 我們可以先使用scan命令對數據庫進行掃描,然后用type命令返回每一個key的類型。
- 對于String類型,我們可以直接使用strlen命令獲取字符串的長度,也就是占用內存的字節(jié)數。
如何刪除大key
如果我們一下子釋放大量的內存,空閑內存塊鏈表操作時間會增加,相應地就會造成Redis主線程阻塞,如果redis主線程發(fā)生了阻塞其他客戶端的請求可能會超時,如果超時的連接越來越多會造成各自異常問題。
因此我們刪除大key這一個動作我們要特別的小心具體要怎么做這里給出兩種方法:
- 漸進式刪除
- 異步刪除(unlink)
漸進式刪除
1.對于string類型
對于這個String類型的大key我們可以使用del直接刪除如果這個他確實很大我們可以使用unlink來進行異步刪除 。這個特別簡單在這里就不演示了。
2.對于Hash類型
對于刪除大的Hash類型,我們可以使用hscan命令每次獲取100個字段,這個個數根據業(yè)務來確定然后我們在使用hdel命令每次刪除一個字段即可。
- 對應golang代碼
package main
import (
"fmt"
"github.com/go-redis/redis"
)
func DelBigHashKey(conn *redis.Client) {
bigKey := "xxx"
cursor := uint64(0)
for {
ScanRet := conn.HScan(bigKey, cursor, "", 100)
keys, c, err := ScanRet.Result()
if err != nil {
panic(err)
}
fmt.Println(keys)
for i := 0; i < len(keys); i++ {
conn.HDel(bigKey, keys[i])
}
if c == 0 {
break
}
cursor = c
}
}
func main() {
conn := redis.NewClient(&redis.Options{
Addr: "101.35.98.26:6379", // url
Password: "", //redis密碼
DB: 0, // 0號數據庫
})
_, err := conn.Ping().Result()
if err != nil {
fmt.Println("ping err :", err)
return
}
DelBigHashKey(conn)
}
3.對應刪除大的List,通過ltrim命令每次迭代刪除少量元素
// DelBigListKey 刪除大的Listkey
func DelBigListKey(conn *redis.Client) {
bigKey := "xxx" //要刪除的大key
for conn.LLen(bigKey).Val() > 0 {
conn.LTrim(bigKey, 0, -101) //每次刪除最右邊100個元素
}
}4.刪除大的Set 可以先使用sscan獲取部分元素,比如每次掃描集合當中100個元素在用srem命令每次刪除一個鍵
func DelBigSetKey(conn *redis.Client) {
bigKey := "xxx" //要刪除的大key
cursor := uint64(0) //游標
for {
keys, c, err := conn.SScan(bigKey, cursor, "", 100).Result()
if err != nil {
panic(err)
}
cursor = c
for i := 0; i < len(keys); i++ {
fmt.Println(conn.SRem(bigKey, keys[i]).Val())
}
if c == 0 {
//刪除完畢
break
}
}
}對于刪除Zset,使用zremrangebyrank命令每次刪除top 100元素
func DelBigZsetKey(conn *redis.Client) {
bigKey := "xxx"
for conn.ZCard(bigKey).Val() > 0 {
conn.ZRemRangeByRank(bigKey, 0, 99)
}
}以上就是針對大key刪除的方案。
異步刪除
除了上面的刪除方案我們可以采用異步刪除的方式可以使用unlink命令代替del來刪除這樣就不會阻塞主線程。
如何禁用keys*、flushdb 、flushall等命令
如果我們想要禁用這些命令我們可以在redis.conf 默認配置文件,找到 SECURITY 區(qū)域,如以下所示:

這樣這些命令就被禁用了。

Redis有兩個原語來刪除鍵。一種稱為DEL,是對象的阻塞刪除。這意味著服務器停止處理新命令,以便以同步方式回收與對象關聯(lián)的所有內存。如果刪除的鍵與一個小對象相關聯(lián),則執(zhí)行DEL命令所需的時間非常短,可與大多數其他命令相媲美
Redis 中的O(1)或O(log_N)命令。但是,如果鍵與包含數百萬個元素的聚合值相關聯(lián),則服務器可能會阻塞很長時間(甚至幾秒鐘)才能完成操作。
基于上述原因,Redis還提供了非阻塞刪除原語,例如UNLINK(非阻塞DEL)以及FLUSHALL和FLUSHDB命令的ASYNC選項,以便在后臺回收內存。這些命令在恒定時間內執(zhí)行。另一個線程將盡可能快地逐步釋放后臺中的對象。
FLUSHALL和FLUSHDB的DEL、UNLINK和ASYNC選項是用戶控制的。這取決于應用程序的設計,以了解何時使用其中一個是個好主意。然而,作為其他操作的副作用,Redis服務器有時不得不刪除鍵或刷新整個數據庫。具體而言,Redis在以下場景中獨立于用戶調用刪除對象:

我們可以將配置文件當中的這些參數設置為yes,也就是懶釋放

總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

