??bigkey是指key對(duì)應(yīng)的value所占的內(nèi)存空間比較大。按照數(shù)據(jù)結(jié)構(gòu)細(xì)分,一般分為字符串類(lèi)型bigkey和非字符串類(lèi)型bigkey。
(1) 字符串類(lèi)型:一般認(rèn)為超過(guò)10KB就是bigkey。
(2) 非字符串類(lèi)型:哈希、列表、集合、有序集合,體現(xiàn)在元素個(gè)數(shù)過(guò)多。
1 bigkey的危害
??(1) 內(nèi)存使用不均勻:例如在Redis集群中,bigkey會(huì)造成節(jié)點(diǎn)的內(nèi)存空間使用不均勻。
??(2) 超時(shí)阻塞:由于Redis單線(xiàn)程的特性,操作bigkey比較耗時(shí),也就意味著阻塞Redis的可能性增大。
??(3) 網(wǎng)絡(luò)擁塞:每次獲取bigkey產(chǎn)生的網(wǎng)絡(luò)流量比較大,假設(shè)一個(gè)bigkey為1MB,每秒訪(fǎng)問(wèn)量為1000,那么每秒產(chǎn)生1000MB的流量。
2 發(fā)現(xiàn)bigkey
??redis-cli --bigkeys可以命令統(tǒng)計(jì)bigkey的分布。
??判斷一個(gè)key是否是bigkey,只需執(zhí)行debug object key查看serializedlength屬性即可,它表示key對(duì)應(yīng)的value序列化之后的字節(jié)數(shù)。
??例如如果執(zhí)行以下命令:
127.0.0.1:6379> debug object msg
Value at:000007F51306AEB0 refcount:1 encoding:raw serializedlength:31 lru:221580
4 lru_seconds_idle:10
??可以發(fā)現(xiàn)serializedlength=31 字節(jié),encoding是embstr,也就是字符串類(lèi)型,可以通過(guò)strlen看以下字符串的字節(jié)數(shù)為1675個(gè)字節(jié)。
127.0.0.1:6379> strlen msg
(integer) 1675
??serializedlength不代表真是的字符大小,它返回對(duì)象使用RDB編碼序列化后的長(zhǎng)度,值偏小。但是對(duì)于排查bigkey具有一定的輔助作用,因?yàn)椴皇敲恳环N數(shù)據(jù)結(jié)構(gòu)都有類(lèi)似strlen的命令,如列表類(lèi)型就沒(méi)有strlen命令。
127.0.0.1:6379> lpush databases mysql oracle redis
(integer) 3
127.0.0.1:6379> strlen databases
(error) WRONGTYPE Operation against a key holding the wrong kind of value
在實(shí)際生產(chǎn)環(huán)境中,發(fā)現(xiàn)bigkey的兩種方式:
(1) 被動(dòng)收集:所謂被動(dòng)收集就是等到出現(xiàn)了問(wèn)題再去分析原因,如命令的慢查詢(xún)或網(wǎng)卡跑滿(mǎn)的情況,通過(guò)分析找到可能的原因是bigkey,這種方式不推薦使用。建議修改Redis客戶(hù)端,當(dāng)拋出異常打印所操作的key,方便排查bigkey。
(2) 主動(dòng)收集:scan + debug object的命令,如果懷疑存在bigkey,可以使用scan命令漸進(jìn)的掃描出所有的key,分別計(jì)算每個(gè)key的serializedlength,找到對(duì)應(yīng)的bigkey進(jìn)行相應(yīng)的處理和報(bào)警,這種方式比較推薦。
SCAN 命令:用于迭代當(dāng)前數(shù)據(jù)庫(kù)中的數(shù)據(jù)庫(kù)鍵。跟keys命令返回全部的數(shù)據(jù)庫(kù)鍵不同,它每次執(zhí)行都只會(huì)返回少量元素,所以可以用于生產(chǎn)環(huán)境。
??SCAN 命令是一個(gè)基于游標(biāo)的迭代器(cursor based iterator): SCAN 命令每次被調(diào)用之后, 都會(huì)向用戶(hù)返回一個(gè)新的游標(biāo), 用戶(hù)在下次迭代時(shí)需要使用這個(gè)新游標(biāo)作為 SCAN 命令的游標(biāo)參數(shù), 以此來(lái)延續(xù)之前的迭代過(guò)程。
127.0.0.1:6379> scan 0
1) "13"
2) 1) "h"
2) "e"
3) "n"
4) "f"
5) "d"
6) "s"
7) "i"
8) "msg"
9) "k"
10) "databases"
127.0.0.1:6379> scan 13
1) "0"
2) 1) "r"
2) "t"
3) "message"
4) "m"
5) "j"
6) "l"
scan 0:第一次迭代使用 0 作為游標(biāo), 表示開(kāi)始一次新的迭代。
第二次迭代使用的是第一次迭代時(shí)返回的游標(biāo), 也即是命令回復(fù)第一個(gè)元素的值 —— 13
在第二次調(diào)用SCAN命令時(shí),命令返回了游標(biāo) 0,這表示迭代已經(jīng)結(jié)束, 整個(gè)數(shù)據(jù)集(collection)已經(jīng)被完整遍歷過(guò)了。
3 刪除bigkey
??使用del命令刪除bigkey通常來(lái)說(shuō)會(huì)阻塞Redis服務(wù)器,在生產(chǎn)環(huán)境中要盡量避免。
??當(dāng)bigkey對(duì)應(yīng)的value越來(lái)越大,刪除的時(shí)間也會(huì)隨著增加。除了string類(lèi)型刪除時(shí)一般不產(chǎn)生阻塞,其他四種數(shù)據(jù)結(jié)構(gòu)的刪除都有可能產(chǎn)生阻塞。
??刪除bigkey要使用漸進(jìn)式遍歷的方式,利用sscan、hscan、zscan命令將若干個(gè)鍵拿出來(lái)。
??實(shí)際中如何避免bigkey:
(1) 在數(shù)據(jù)結(jié)構(gòu)的選擇和設(shè)計(jì)應(yīng)合理,如出現(xiàn)了bigkey,考慮可不可以?xún)?yōu)化(例如拆分?jǐn)?shù)據(jù)結(jié)構(gòu))盡量讓bigkey消失在業(yè)務(wù)中。
(2) 如果bigkey不可避免,也要考慮是不是每次都要將所有的元素都取出來(lái),有時(shí)僅僅只需要hmget,而不是hgetall。
(3) Redis 4.0支持lazy delete free模式,刪除bigkey不會(huì)阻塞Redis。
小結(jié)
(1) bigkey會(huì)造成數(shù)據(jù)傾斜,網(wǎng)絡(luò)擁塞和超時(shí)阻塞,在實(shí)際環(huán)境中要重視bigkey。
(2) bigkey刪除要使用漸進(jìn)式遍歷方式,防止出現(xiàn)Redis阻塞的情況。
(3) Redis 4.0支持lazy delete free,它不會(huì)阻塞Redis。
??本文完
??注:本文參考《Redis開(kāi)發(fā)與運(yùn)維》,如發(fā)現(xiàn)錯(cuò)誤,請(qǐng)指正!