Redis大Key分析和解決最佳實(shí)踐
神州信息
劉彬
1.
概述
我們先假定有如下場(chǎng)景:某稅務(wù)局一線(xiàn)運維收到客戶(hù)反饋通知,說(shuō)系統查詢(xún)某機構對申報信息極為緩慢。于是開(kāi)發(fā)人員找到一個(gè)出問(wèn)題的機構A,通過(guò)搜索日志系統找到系統操作Redis時(shí)間比較久,并且從日志系統搜到一些Redis 含詢(xún)超時(shí)的異常。最后我們定位到的原因如下:
在申報接口的時(shí)候系統通過(guò)Redis做了一個(gè)級存,記錄機構的申報信息,Redis數據結構:key=機構、IDvalue=申報列表,而Redis查詢(xún)發(fā)現多了許多大key,體現在一個(gè)機構ID一天有上萬(wàn)甚至幾十萬(wàn)的申報信息。我們通常將此類(lèi)問(wèn)題稱(chēng)為Redis大Key問(wèn)題。
2.
Redis大Key基本概念及場(chǎng)景
所謂的大key問(wèn)題是某個(gè)key對應的value比較大,所以本質(zhì)上是大value問(wèn)題,key往往是開(kāi)發(fā)過(guò)程中可以自行設置,可以控制大小,value往往不受程序控制跟業(yè)務(wù)場(chǎng)景有關(guān)系,因此可能導致value較大。
2.1基本概念
在Redis中,大key指的是key對應的value值所占的內存空間比較大:
● value是string類(lèi)型,大小建議控制在10kb以?xún)取?/p>
● valve是hash、list、set、zset等集合類(lèi)型,元素個(gè)數建議不要超過(guò) 5000(或者1萬(wàn)、幾萬(wàn))。上述的定義并不絕對,主要是根據value的大小和元索個(gè)數來(lái)確定,業(yè)務(wù)也可以很據自己的場(chǎng)景確定標淮。
2.2常見(jiàn)場(chǎng)景
大key的產(chǎn)生往往是業(yè)務(wù)方設計不合理,沒(méi)有預見(jiàn)vaule的動(dòng)態(tài)增長(cháng)問(wèn)題。
通常有幾類(lèi)此較經(jīng)典的場(chǎng)錄:
● 一直往value存放數據,沒(méi)有刪除及過(guò)期機制。
● 數據沒(méi)有合理做分片,將大key變成以一個(gè)個(gè)小key。
3.
Redis大Key帶來(lái)的影響
● 客戶(hù)端超時(shí)阻塞。由于Redis單線(xiàn)程的特性,操作大key的通常比較耗時(shí),也就意味著(zhù)阻塞Redis可能性越大,這樣會(huì )造成容戶(hù)瑞阻塞或者引起故障切換,會(huì )出現各種Redis慢查詢(xún)。
● 內存空間不均勻。集群模式在slot分片均勻情況下,會(huì )出現數據和查詢(xún)傾斜情況,部分有大key的Redis節點(diǎn)占用內行多、QPS高。
● 引發(fā)網(wǎng)絡(luò )阻塞。每次獲取大key產(chǎn)生的網(wǎng)絡(luò )流量按大,如果一個(gè)key的大小為1MB每秒訪(fǎng)問(wèn)量為1000,那么行秒會(huì )產(chǎn)生1000MB的流量。這對于普通千兆網(wǎng)卡的服務(wù)器說(shuō)是災難性的。
● 阻塞工作線(xiàn)程。執行大key刪除時(shí),在低版本Redis中可能阻塞線(xiàn)程。
4.
Redis大Key如何檢測
● 改寫(xiě)Redis客戶(hù)端,在sdk中加入埋點(diǎn),實(shí)時(shí)上報數據給Redis大key 檢測平臺、監控告警。
● scan+debug object bigkey命令,循環(huán)遍歷Redis key序列化后的長(cháng)度。debug object bigkey可能會(huì )比較慢,它存在阻塞Redis的可能,建議在從節點(diǎn)執行該命令,官方不推薦。
● scan+memory usage。該命令是在Redis 4.0+以后提供的,可以循環(huán)遍歷統計計算每個(gè)鍵值的字節數。
● 通過(guò)python腳本迭代的scan key。對每次scan的內容進(jìn)行判斷是否為大key。
● Redis-cli --bigkeys??梢哉业侥硞€(gè)Redis 實(shí)例5種數據類(lèi)型(string、hash、list、set、zset)的最大key。但如果Redis key 比較多,執行該命令會(huì )比較慢,建議在從節點(diǎn)執行該命令。
● rdbtools開(kāi)源工具包。rdbtools是python寫(xiě)的一個(gè)第三方開(kāi)源工具,用來(lái)解析Redis快照文件,Redis實(shí)例上執行bgsave,然后對dump出;來(lái)的rdb文件進(jìn)行分析,找到其中的大key。
例如:rdb dump.rdb -c memory --byes 10240 -f Redis.csv
從dump.rdb 快照文件統計 (bgsave),將所有>10kb的key輸出到一個(gè)csv文件。
5.
Redis大Key如何刪除
如果對這類(lèi)大key直接使用del命今進(jìn)行刪除,會(huì )導致長(cháng)時(shí)間阻塞,甚至崩潰,因為del命令在刪除集合類(lèi)型數據時(shí),時(shí)間復雜度為O(M),M是集合中元素的個(gè)數。Redis是單線(xiàn)程的,單個(gè)命令執行時(shí)間過(guò)長(cháng)就會(huì )阻塞其他命令,容易引起雪崩,穩妥的建議如下:
主動(dòng)刪除大Key
一、分批次漸近刪除
一般來(lái)說(shuō),對于string數據類(lèi)型使用del命令不會(huì )產(chǎn)生阻塞。其它數據類(lèi)型分批刪除,通過(guò)scan命令遍歷大key,每次取得少部分元素進(jìn)行刪除,然后再獲取和刪除下一批元素.對Hash,Sorted Set, List. Set 分別處理、思路相同,先對key改名進(jìn)行邏輯刪除,使客戶(hù)端無(wú)法使用原key,然后使用批量小步刪除。
● 刪除大Hash
步驟:(1)key改名,相當于邏輯上刪除key,任何Redis命令都訪(fǎng)問(wèn)不了該key。(2)小步多批次刪除。
偽代碼:
● 刪除大List
偽代碼:
● 刪除大Set
偽代碼:
● 刪除大Sorted Set
偽代碼:
二、采用unlink+bigkey異步非阻塞刪除,這個(gè)命令是在Redis 4.0+提供的代替del命令,不會(huì )阻塞主線(xiàn)程。
被動(dòng)刪除大Key
被動(dòng)刪除是指利用Redis自身的key消除策略,配置lazyfree情性刪除。但是參數默認是關(guān)閉的??膳渲萌缦聟甸_(kāi)啟:
6.
Redis大Key如何設計與優(yōu)化
主要針對以下兩種經(jīng)典場(chǎng)景進(jìn)行優(yōu)化:
單個(gè)key 存儲的 value 很大(超過(guò) 10kb)
1)從業(yè)務(wù)角度評估,value中只存儲有用的字段,盡量去掉無(wú)用的字段。
2)可以考點(diǎn)在應用層先對value進(jìn)行壓縮,比如采用LZ4/Snappy之類(lèi)的壓縮算法,配合Redis客戶(hù)端序列化配置,可以無(wú)侵入完成value的壓縮。.
3)value設計的時(shí)候越小越好,關(guān)聯(lián)的數據分不同的key進(jìn)行存儲。
4)大key分拆成幾個(gè)key-value,使用multiGet獲取值,這樣分拆的意義在于分拆單次操作的壓力.將操作壓力拼攤到多個(gè)Redis實(shí)例中,降低對單個(gè)Redis的IO影響。
5)對Redis集群進(jìn)行擴容。
集合數據類(lèi)型hash. list, set. sorted set等存儲過(guò)多的元素(超過(guò)5000個(gè))
類(lèi)似于場(chǎng)景一中的第一個(gè)做法,可以將這些元素分拆:
以hash為例,原先的正常存取流程是hget(hashKey,field) ;hset(hashkey,field,value)現在,我們可以分拆構建一個(gè)新的 newHashkey,具體做法:固定一個(gè)桶的數量,比如10000每次存取的時(shí)候,先在本地計算field的hash值,取模10000,確定了該field落在哪個(gè)newHashkey上。
set、sorted、list也可以采用類(lèi)似做法。