在Redis的運維使用過程中你遇到過那些問題搬男,又是如何解決的呢?本文收集了一些Redis的常見問題以及解決方案彭沼,與大家一同探討缔逛。
碼字不易,歡迎大家轉(zhuǎn)載,煩請注明出處褐奴;謝謝配合
你的Redis有bigkeys嗎按脚?
什么是bigkeys
bigkeys是指key不恰當設(shè)定,抑或是key對應(yīng)的value值占用內(nèi)存空間過大敦冬;具體表現(xiàn)為以下幾種情形:
- key值不恰當設(shè)定(比較少見)辅搬,key設(shè)定冗長
- String類型 value值長度過大
- Hash,List脖旱,Set堪遂,Zset 包含元素個數(shù)過多
bigkeys有什么危害
為什么我們必須警惕bigkey呢?其實bigkey主要有以下幾個方面的危害:
- 內(nèi)存使用不均勻萌庆,例如:在Redis-Cluster模式中溶褪,bigkey會造成節(jié)點內(nèi)存使用不均勻。
- 超時阻塞践险,由于Redis是單線程架構(gòu)猿妈,操作bigkey耗時較長,有可能造成Redis阻塞巍虫。
- 網(wǎng)絡(luò)擁阻彭则,例如:一個bigkey占用空間是1M,每秒訪問1000次占遥,將造成1000M的流量俯抖,可能造成打滿機器帶寬。
當然瓦胎,如果bigkey訪問頻率不高蚌成,也僅會造成節(jié)點間內(nèi)存使用不均;而當bigkey訪問頻繁時凛捏,其帶來的影響是不可想象的担忧,所以日常在開發(fā)運維的過程中應(yīng)該警惕bigkey的存在。
如何找到bigkeys
了解到bigkey危害坯癣,我們該如何發(fā)現(xiàn)bigkeys呢瓶盛?
Redis在設(shè)計之初就考慮到bigkeys的問題,我們可以使用 redis-cli --bigkeys 來發(fā)現(xiàn)bigkeys的分布情況示罗;之后你如果想進一步了解bigkeys的具體情況可以使用 debug object <key> 來確定該key的具體信息惩猫。參考以下示例:
利用redis-cli --bigkeys找到bigkey,具體生產(chǎn)環(huán)境執(zhí)行時強烈建議在從節(jié)點實行蚜点,如果擔心OPS太高轧房,可以使用 -i 0.1 ,表示每100條scan命令休眠0.1秒绍绘;其實該命令實現(xiàn)的原理就是利用我們常用的scan + type + strlen/hlen/llen/scard/zcard 命令實現(xiàn)的奶镶,具體可以從redis-cli.c的源碼中探尋迟赃。
[root@VM_0_16_centos src]# redis-cli -p 6380 --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 'h' with 1 bytes
[00.00%] Biggest string found so far 'hello' with 105 bytes
[00.00%] Biggest string found so far 'heml' with 1434 bytes
-------- summary -------
Sampled 3 keys in the keyspace!
Total key length in bytes is 10 (avg len 3.33)
Biggest string found 'html' has 1434 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)
3 strings with 1540 bytes (100.00% of keys, avg size 513.33)
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)
執(zhí)行結(jié)果是發(fā)現(xiàn)String類型的"html"為bigkey,我們緊接著來了解"html"的具體信息厂镇;使用
debug object <key> 命令纤壁,還有如果是對于元素個數(shù)較多的數(shù)據(jù)結(jié)構(gòu),該命令可能會阻塞redis實例捺信,所以強烈建議在從節(jié)點執(zhí)行
[root@VM_0_16_centos src]# redis-cli -p 6380
127.0.0.1:6379> debug object html
Value at:0x7f0b13a665c0 refcount:1 encoding:raw serializedlength:251 lru:12181323 lru_seconds_idle:229
127.0.0.1:6379> strlen html
(integer) 1434
我們發(fā)現(xiàn)key為"html"的String類型的value長達1434個字節(jié)酌媒,以上便是演示查找bigkeys的過程;除了以上方式我們可以在bigkeys影響redis正常提供服務(wù)之前迄靠,通過 scan + debug object 對懷疑的bigkeys進行逐個檢查秒咨。
當然你如果擔心執(zhí)行相關(guān)命令會對正式環(huán)境有一定的影響,你也可以通過對RDB進行備份掌挚,然后根據(jù)RDB文件的結(jié)構(gòu)雨席,對RDB中的數(shù)據(jù)進行逐個分析同樣的可以找到bigkey,不過這種方式的開發(fā)成本會有些高疫诽;使用者可以依據(jù)自己的實際情況來酌情判斷。
如何處理bigkeys
經(jīng)過一番折騰旦委,我們終于找到bigkeys了奇徒,那么我們應(yīng)該如何處理它呢?
在Redis4.0之前版本缨硝,由于DEL命令是同步刪除的摩钙,針對String類型的bigkeys確實可以使用DEL命令,刪除速度相對較快查辩,一般不會阻塞redis胖笛;然而對于元素個數(shù)較多的數(shù)據(jù)結(jié)構(gòu),使用DEL命令來刪除可能會阻塞redis實例宜岛;針對 Hash 結(jié)構(gòu)长踊,我們可以利用 HSCAN + HDEL 刪除元素的成員,成員刪除之后再利用 DEL 刪除key萍倡;其余數(shù)據(jù)類似都是漸進的方式先刪除成員身弊,再刪除key。
Redis4.0版本之后則支持了Lazy Delete Free模式列敲,你可以使用 UNLINK 命令來刪除bigkeys阱佛,它的實現(xiàn)是異步的,具體可以從redis-cli.c的源碼中探尋戴而,你需要先確認打開了lazyfree相關(guān)配置凑术。
bigkeys總結(jié)
bigkeys的表現(xiàn)形式是內(nèi)存分配不均;頻繁操作的實際影響是有可能造成超時阻塞所意,網(wǎng)絡(luò)擁阻淮逊;解決思路是事前監(jiān)控催首,事中找到bigkeys,并通過正確的方式刪除bigkeys壮莹。
你的Redis有hotkeys嗎翅帜?
什么是hotkeys?
hotkeys是在Redis實例中某些key的操作頻次遠高于其他key命满,那么這些被頻繁操作的熱點key我們就稱之為hotkeys涝滴。
hotkeys有什么危害?
hotkeys有什么危害呢胶台?以Redis-Cluster模式為例歼疮,存在hotkeys的節(jié)點,將面臨以下挑戰(zhàn):
- 請求分配不均诈唬,存在hotkeys的節(jié)點面臨較大的訪問壓力
- 緩存擊穿韩脏,hotkeys過期時,大量請求將直接導(dǎo)向DB
- 緩存雪崩铸磅,擊垮存在hotkeys的節(jié)點赡矢,導(dǎo)致不能正常提供服務(wù)
如何發(fā)現(xiàn)hotkeys呢?
Redis4.0之后客戶端提供了hotkeys發(fā)現(xiàn)的相關(guān)命令阅仔,我們可以通過 redis-cli --hotkeys 來發(fā)現(xiàn)hotkeys吹散;
Redis4.0之前我們也可以通過客戶端,代理端八酒,服務(wù)端空民,機器端等多個方面來發(fā)現(xiàn)hotkeys:
- 在客戶端建立全局字典表,對key和調(diào)用次數(shù)進行統(tǒng)計羞迷;缺點:侵入客戶端
- 如果你的集群是通過proxy + redis 的方式搭建的界轩,那你可以很方便的從代理端對key和調(diào)用次數(shù)進行監(jiān)控;缺點:限制代理模式的集群
- 在服務(wù)端可以利用 monitor衔瓮,對服務(wù)端接收的請求進行監(jiān)控浊猾;缺點:侵入服務(wù)端,在高并發(fā)情況下會使內(nèi)存暴增热鞍,適合短時間使用
- 如果你不想侵入服務(wù)端與客戶端与殃,可以對服務(wù)端接收的請求進行抓包,分析以及監(jiān)控碍现;例如使用ELK(Elasticsearch + Logstach + kinbana)用packetbeat進行抓包幅疼。
如何處理hotkeys?
我們了解到hotkeys的危害昼接,并可以通過技術(shù)手段找到hotkeys以后爽篷,我們該怎么對系統(tǒng)做優(yōu)化呢?
首先針對hotkeys過期慢睡,面臨的重建問題逐工,可以使用以下有效手段來盡可能的減少key重建的過程:
- 設(shè)置互斥鎖铡溪,保證由一個線程完成熱點key的重建,避免大量的請求直接導(dǎo)向DB
- "永不過期"泪喊,將hotkeys的過期時間設(shè)置較長的時間棕硫,或者永不過期;等待hotkeys觸發(fā)的熱點事件過去后再考慮過期袒啼。
針對Redis集群的優(yōu)化哈扮,包括但不限于以下幾種方式:
- hotkeys表現(xiàn)就是請求分配不均;我們可以以此為出發(fā)點蚓再,來想辦法使請求盡可能的分布平均滑肉;例如:利用<hotkeys_n,value> ,n為隨機數(shù),盡可能的多個實例都有該數(shù)據(jù)摘仅,在訪問時在n的范圍內(nèi)取隨機數(shù)以此來分攤請求靶庙;此方式需要一定的代碼改造;
- 本地緩存娃属,此方式需要對熱點信息有預(yù)知六荒,例如:電商產(chǎn)品大促,熱點產(chǎn)生在可以預(yù)知的范圍內(nèi)矾端,便可以考慮此方式掏击;
- 集群的熱點數(shù)據(jù)的節(jié)點的擴容,此方式原理同第一種方式相同须床,不同點在于不需要對代碼進行改造铐料,而是直接增加hotkeys對應(yīng)節(jié)點的數(shù)據(jù)副本渐裂,使多個節(jié)點都具備提供該數(shù)據(jù)的讀取能力豺旬,以此來均衡請求。
hotkeys總結(jié)
hotkeys的表現(xiàn)形式是請求的分配不均柒凉,問題惡化將導(dǎo)致Redis集群請求傾斜族阅,甚至集群雪崩,我們可以通過多種途徑來均衡請求膝捞,避免單個節(jié)點過熱坦刀;如果hotkeys的超時實現(xiàn)過短,可能會導(dǎo)致大量請求涌入到DB蔬咬,并發(fā)重建key鲤遥,可以通過合理的鎖機制或者設(shè)置合理的超時時間來避免。
Redis緩存穿透
什么是緩存穿透
緩存穿透是大量請求的key在緩存中沒有林艘,直接請求到DB盖奈,使緩存失去保護數(shù)據(jù)庫的作用;例如:黑客刻意構(gòu)建大量緩存中沒有的key狐援,導(dǎo)致每次處理請求都需要去訪問數(shù)據(jù)庫钢坦。
正常緩存處理流程
緩存穿透處理流程
緩存穿透的流程便是故意構(gòu)建緩存中沒有的key導(dǎo)致究孕,所有的請求必須查庫;緩存失去其保護數(shù)據(jù)庫的意義爹凹。
解決緩存穿透
通過以上流程圖我們對緩存穿透有了一定的了解厨诸,那該如何解決此類問題呢?通常解決的方式有兩種:
(1) 對空值進行緩存禾酱,設(shè)置較短的失效時間微酬;
分析:我們對null進行緩存,Redis需要更大的內(nèi)存空間宇植;此方案適用于請求key變化不頻繁的情況得封;如何黑客惡意攻擊,每次構(gòu)建的不同的請求key指郁,這種方案并不能從根本上解決此問題忙上。
(2) 使用布隆過濾器,布隆過濾器優(yōu)勢在于檢索一個元素是否在一個集合內(nèi)闲坎;我們可以利用布隆過濾器來判斷請求的key是否在合理的范圍內(nèi)纷纫,如果不存在,則直接過濾掉吧寺。
分析:可以利用Redis的 BitMap來實現(xiàn)布隆過濾器隅津,用其來緩存目標數(shù)據(jù)集變化不頻繁,而請求key變化頻繁的情況绣溜。
Redis緩存雪崩
什么是緩存雪崩
緩存雪崩是指由于緩存集中過期或者緩存不可用慷彤,導(dǎo)致大量請求直接導(dǎo)向數(shù)據(jù)庫。在高并發(fā)的情況下怖喻,巨大的請求量有可能導(dǎo)致數(shù)據(jù)庫的崩潰底哗,甚至導(dǎo)致整個應(yīng)用體系的全盤崩潰;緩存失去保護是數(shù)據(jù)庫的作用锚沸,而巨大導(dǎo)致流量流向數(shù)據(jù)庫就是緩存雪崩的表現(xiàn)形式跋选。
如何預(yù)防及避免
- 設(shè)置合理的過期策略,避免緩存集中過期哗蜈。
- hotkeys分片存儲前标,避免請求數(shù)據(jù)的傾斜,導(dǎo)致緩存距潘。
- hotkeys設(shè)置合理的過期時間或者“永不過期”炼列。
Redis阻塞
我們知道Redis是單線程模型,如果線上Redis發(fā)生阻塞對整個應(yīng)用將是毀滅性的音比;那什么原因會導(dǎo)致Redis阻塞呢俭尖?
API或數(shù)據(jù)結(jié)構(gòu)使用不合理
常見的是在生產(chǎn)上執(zhí)行時間復(fù)雜度高的命令如: KEYS,可以通過RENAME 方式將命令修改為不易猜測的硅确,避免開發(fā)運維人員的不當執(zhí)行目溉。
數(shù)據(jù)結(jié)構(gòu)的不合理明肮,如存在頻繁操作bigkeys,有可能造成阻塞缭付,將bigkeys拆分成成員較小的key柿估。
CPU飽和
Redis單實例OPS可以到達平均10W+左右,如果你的Redis實例OPS已經(jīng)達到較高的數(shù)值陷猫,那你可以考慮集群的水平擴展秫舌,來降低實例的OPS;但是你的Redis實例OPS不高绣檬,CPU使用率較高足陨,那你應(yīng)該檢查應(yīng)用是否使用了時間復(fù)雜度較高的命令。
持久化阻塞
我們知道Redis可以進行持久化來防止數(shù)據(jù)的丟失娇未;RDB方式墨缘,主進程會fork一個共享內(nèi)存子進程來創(chuàng)建RDB文件,如果fork耗時過長零抬,必然將阻塞主進程镊讼。
AOF刷盤,通常我們設(shè)置的刷盤策略是everysec平夜,但由于磁盤壓力過大蝶棋,fsync有可能耗時較長,當時間大于1秒時忽妒,為了保證數(shù)據(jù)安全玩裙,下次fsync調(diào)用將阻塞知道上次調(diào)用結(jié)束。
其他原因
CPU競爭:Redis是CPU密集型應(yīng)用段直,應(yīng)避免跟其他CPU密集型應(yīng)用部署在一起
內(nèi)存交換:Redis由于從內(nèi)存中直接讀取吃溅,所以響應(yīng)速度很快;當內(nèi)存嚴重不足時坷牛,可能會存在內(nèi)存交換罕偎,這將影響Redis的執(zhí)行效率很澄;通過cat /proc/$pid/smaps | grep Swap 來確認是否有頻繁的內(nèi)存交換京闰。
網(wǎng)絡(luò)問題:連接數(shù)限制或者網(wǎng)絡(luò)延時等也有可能導(dǎo)致阻塞
Redis淘汰策略
當Redis的內(nèi)存使用達到限制時(可通過maxmemory <bytes>設(shè)置),會根據(jù)根據(jù)淘汰策略來移除Keys甩苛;有如下淘汰策略:
- allkeys-random:在所有keys中隨機移除
- allkeys-lru:在所有keys中使用lru移除
- allkeys-lfu:在所有keys中使用lfu移除
- volatile-random:在過期keys中隨機移除
- volatile-lru:在過期keys中使用lru移除
- volatile-lfu:在過期keys中使用lfu移除
- volatile-ttl:移除即將過期
- noevction:不移除任何key蹂楣,空間不足時將拋出error
lru:Least Recently Used 最近最少使用
lfu:Least Frequently Used 最不經(jīng)常使用
總結(jié)
本文介紹了bigkeys,hotkeys,緩存穿透,緩存雪崩讯蒲,阻塞等問題痊土;然而我們在實際應(yīng)用中難免會遇到各種各樣的問題,本文難以一一列舉墨林;但是面對問題我們要沉著冷靜赁酝,了解清楚問題的現(xiàn)象與本質(zhì)犯祠,找到問題的癥結(jié)所在,隨后在對癥下藥便可以解決問題酌呆。