如何應(yīng)對(duì) Redis 大 Key 問(wèn)題
日常業(yè)務(wù)運(yùn)行過(guò)程中,Redis 實(shí)例經(jīng)常因各種 Big keys / Hot Keys 的問(wèn)題未及時(shí)處理,導(dǎo)致服務(wù)性能下降、訪問(wèn)超時(shí)、用戶體驗(yàn)變差,甚至可能造成實(shí)例大范圍故障 。
這篇文章,我們聊聊生產(chǎn)環(huán)境,如何應(yīng)對(duì) Redis 大 Key 問(wèn)題。
圖片
一、什么是大 key
大 Key 具體表現(xiàn)為 Redis 中的 Key 對(duì)應(yīng)的 Value 很大,占用 Redis 空間比較大,本質(zhì)上是大 Value 問(wèn)題。
對(duì)于 Redis 中不同的數(shù)據(jù)結(jié)構(gòu)類型,常見(jiàn)示例如下所示:
對(duì)于 String 類型的 Value 值,值超過(guò) 10MB(數(shù)據(jù)值太大)。
對(duì)于 Set 類型的 Value 值,含有的成員數(shù)量為 10000 個(gè)(成員數(shù)量多)。
對(duì)于 List 類型的 Value 值,含有的成員數(shù)量為 10000 個(gè)(成員數(shù)量多)。
對(duì)于 Hash 格式的 Value 值,含有的成員數(shù)量 1000 個(gè),但所有成員變量的總 Value 值大小為 1000 MB(成員總的體積過(guò)大)。
在 Redis 的實(shí)際應(yīng)用中,大 Key 問(wèn)題的定義和評(píng)判標(biāo)準(zhǔn)并非固定不變,而是需要結(jié)合具體業(yè)務(wù)場(chǎng)景和性能需求進(jìn)行綜合考量。
例如,在高并發(fā)、低延遲的敏感場(chǎng)景下,即使 10 KB 的數(shù)據(jù)也可能被視為大 Key;而在低并發(fā)、高吞吐量的離線處理環(huán)境中,大 Key 的閾值可能放寬至 100 KB 甚至更高。
因此,在 Redis 的設(shè)計(jì)和使用過(guò)程中,應(yīng)該基于業(yè)務(wù)特性和性能指標(biāo)來(lái)制定合理的大 Key 評(píng)估標(biāo)準(zhǔn)。
二、大 key 有什么影響
Redis 是單線程執(zhí)行命令 ,當(dāng)前面的任務(wù)完成不了,那后面的命令就會(huì)阻塞,從而導(dǎo)致如下的結(jié)果:
1.請(qǐng)求響應(yīng)時(shí)間上升,超時(shí)阻塞。
Redis 是單線程架構(gòu),操作大 Key 耗時(shí)較長(zhǎng),可能造成請(qǐng)求阻塞。
2.同步中斷或主從切換
內(nèi)存不足時(shí),對(duì)大 Key 進(jìn)行驅(qū)逐操作或者 rename 一個(gè)大 Key,容易長(zhǎng)時(shí)間阻塞主庫(kù),進(jìn)而可能引發(fā)同步中斷或主從切換。
3.網(wǎng)絡(luò)擁塞
一個(gè)大 Key 占用空間是 1MB,每秒訪問(wèn)1000 次,就有1000 MB 的流量,可能造成實(shí)例或局域網(wǎng)的帶寬被占滿,自身服務(wù)變慢,同時(shí)影響其他服務(wù)。
4.內(nèi)存使用不均勻
在 Redis 集群架構(gòu)中,某個(gè)數(shù)據(jù)分片的內(nèi)存使用率遠(yuǎn)超其他數(shù)據(jù)分片,內(nèi)存資源無(wú)法達(dá)到均衡。另外,Redis 內(nèi)存可能達(dá)到 maxmemory 參數(shù)定義的上限,導(dǎo)致重要的 Key 被逐出,甚至引發(fā)內(nèi)存溢出。
需要強(qiáng)調(diào)的是:
對(duì)于 Java 應(yīng)用來(lái)講,高并發(fā)場(chǎng)景 大 Key 問(wèn)題容易導(dǎo)致應(yīng)用服務(wù)器 CPU Load / 內(nèi)存占用飆高。
圖片
如圖,這個(gè)一個(gè)非常標(biāo)準(zhǔn)的通過(guò) redisTemplate 查詢用戶緩存信息的方法。
但當(dāng)用戶 DTO 對(duì)象占用內(nèi)存大小達(dá)到 300k ~ 500k 時(shí),并發(fā)高情況下,海量 UserDTO 對(duì)象會(huì)在新生代產(chǎn)生,對(duì)象序列化 和 GC 線程會(huì)大量占用 CPU 資源,導(dǎo)致 CPU Load 飆高 ,最終應(yīng)用線程大面積阻塞。
三、大 key 是如何產(chǎn)生的
1.錯(cuò)誤的技術(shù)選型
比如使用 String 類型的 Key 存放大體積二進(jìn)制文件型數(shù)據(jù),從而造成 key 對(duì)應(yīng)的 value 值特別大 ;
2.List 、Set 數(shù)據(jù)類型數(shù)據(jù)未清理
如圖,我們經(jīng)常使用 Redis List 作為消息隊(duì)列,在實(shí)際使用中經(jīng)常出現(xiàn)如下問(wèn)題:生產(chǎn)者發(fā)送消息過(guò)快,但消費(fèi)者消費(fèi)消息速度低,導(dǎo)致數(shù)據(jù)堆積占用大量?jī)?nèi)存空間 。
3.數(shù)據(jù)沒(méi)有合理做分片
業(yè)務(wù)上線前,對(duì)業(yè)務(wù)分析不準(zhǔn)確,沒(méi)有對(duì) Key 中的成員進(jìn)行合理的拆分,造成個(gè)別 Key 中的成員數(shù)量過(guò)多。
四、如何找到大 key
1.bigkeys 命令
執(zhí)行 redis-cli 命令時(shí)帶上–bigkeys 選項(xiàng),對(duì)整個(gè)數(shù)據(jù)庫(kù)中的鍵值對(duì)大小情況進(jìn)行統(tǒng)計(jì)分析,統(tǒng)計(jì)每種數(shù)據(jù)類型的鍵值對(duì)個(gè)數(shù)以及平均大小。
此外,這個(gè)命令執(zhí)行后,會(huì)輸出每種數(shù)據(jù)類型中最大的 bigkey 的信息:
- 對(duì)于 String 類型來(lái)說(shuō),會(huì)輸出最大 bigkey 的字節(jié)長(zhǎng)度
- 對(duì)于集合類型來(lái)說(shuō),會(huì)輸出最大 bigkey 的元素個(gè)數(shù)
圖片
bigkeys 是通過(guò)掃描數(shù)據(jù)庫(kù)來(lái)查找的,在執(zhí)行的過(guò)程中,會(huì)對(duì) Redis 實(shí)例的性能產(chǎn)生影響。
- 主從集群,建議在從節(jié)點(diǎn)上執(zhí)行該命令,避免阻塞主節(jié)點(diǎn)。
- 沒(méi)有從節(jié)點(diǎn)情況下,在 Redis 實(shí)例業(yè)務(wù)壓力的低峰階段進(jìn)行掃描查詢,以免影響到實(shí)例的正常運(yùn)行。
2.監(jiān)控平臺(tái)
公有云或者公司內(nèi)部架構(gòu)部門(mén)一般都有監(jiān)控平臺(tái),可以可視化分析 Redis 服務(wù)監(jiān)控指標(biāo)。
如下圖是阿里云的 Redis 監(jiān)控大 Key 分析界面 。
圖片
假如是架構(gòu)部門(mén)自己的監(jiān)控平臺(tái),可以添加 Redis 的 Key 監(jiān)控統(tǒng)計(jì)。
下圖是UMP 監(jiān)控平臺(tái)的設(shè)計(jì)思路:
圖片
流程如下:
- 業(yè)務(wù)系統(tǒng)引入通 UMP SDK ,當(dāng)業(yè)務(wù)系統(tǒng)運(yùn)行時(shí),SDK 會(huì)將日志文件(JVM、TP 、HeatBeat)寫(xiě)到磁盤(pán) ;
- FileBeat 讀取日志文件,發(fā)送到 Kafka ;
- UMP 計(jì)算服務(wù) 從 Kafka 中獲取消息,根據(jù)消息類型,執(zhí)行分析邏輯(JVM、TP 、HeatBeat );
- 計(jì)算完成之后,指標(biāo)結(jié)果數(shù)據(jù)存儲(chǔ)到 Hbase,MySQL 用于存儲(chǔ)元數(shù)據(jù),Redis 用于存儲(chǔ)臨時(shí)計(jì)算數(shù)據(jù) ;
- 研發(fā)人員登錄控制臺(tái)查看監(jiān)控信息 ,核心的監(jiān)控?cái)?shù)據(jù)存儲(chǔ)在 Hbase 中,通過(guò) HighChart 組件渲染。
UMP 可以對(duì)應(yīng)用端的 Redis 操作實(shí)現(xiàn)全面的監(jiān)控,包括命令超時(shí)、Key大小、使用頻率等關(guān)鍵指標(biāo)。
五、如何解決大 key 問(wèn)題
1.清理無(wú)效的數(shù)據(jù)
主要針對(duì) list 和 set 這種類型,在使用的過(guò)程中,list 和 set 中對(duì)應(yīng)的內(nèi)容不斷增加,需要定時(shí)的對(duì) list 和 set 進(jìn)行清理。
2.壓縮對(duì)應(yīng)的大 Key 的 Value
通過(guò)序列化或者壓縮的方法對(duì) value 進(jìn)行壓縮,使其變?yōu)檩^小的 value,但是如果壓縮之后如果對(duì)應(yīng)的 value 還是特別大的話,就需要使用拆分的方法進(jìn)行解決。
3.針對(duì)大 Key 進(jìn)行拆分
通過(guò)將 BigKey 拆分成多個(gè)小 Key 的鍵值對(duì),并且拆分后的對(duì)應(yīng)的 value 大小和拆分成的成員數(shù)量比較合理,然后進(jìn)行存儲(chǔ)即可,在獲取的時(shí)候通過(guò) get 不同的 key 或是用 mget 批量獲取存儲(chǔ)的鍵值對(duì)。
4.實(shí)時(shí)監(jiān)控 Redis 內(nèi)存、帶寬及 Key 增長(zhǎng)變化趨勢(shì)
通過(guò)監(jiān)控系統(tǒng),監(jiān)控 Redis 中的內(nèi)存占用大小和網(wǎng)絡(luò)帶寬的占用大小,以及固定時(shí)間內(nèi)的內(nèi)存占用增長(zhǎng)率,當(dāng)超過(guò)設(shè)定的閾值的時(shí)候,進(jìn)行報(bào)警通知處理。