感覺Redis變慢了,這些可能的原因你查了沒 ?
前言
本期繼續(xù)分享關(guān)于Redis的知識(shí),讓你掌握在Redis變慢后不會(huì)慌張,冷靜下來分析問題,為了方便閱讀,文章分為上下兩篇!
Redis 作為一款業(yè)內(nèi)使用率最高的內(nèi)存數(shù)據(jù)庫,其擁有非常高的性能,單節(jié)點(diǎn)的QPS壓測(cè)能達(dá)到18萬以上。但也正因此如此,當(dāng)應(yīng)用訪問 Redis 時(shí),如果發(fā)現(xiàn)響應(yīng)延遲變大時(shí)就會(huì)給業(yè)務(wù)帶來非常大的影響。
比如在日常使用Redis時(shí),肯定或多或少都遇到過下面這種問題:
圖片
大部分兄弟面對(duì)這種訪問變慢問題的排查就會(huì)一頭霧水,不知道從哪里下手才好,因?yàn)椴焕斫?Redis 的架構(gòu)體系、核心功能的實(shí)現(xiàn)原理甚至一些命令的使用限制等。
今天就可能引起Redis變慢的原因一一分析,上篇看完后你將會(huì)形成一個(gè)比較完整的排查思路方案!
圖片
Redis真的變慢了嗎?
當(dāng)我們遇到服務(wù)響應(yīng)比較慢時(shí),往往需要先排查內(nèi)部原因,先弄清楚是不是Redis服務(wù)導(dǎo)致的,我們大部分系統(tǒng)可能涉及較長的鏈路和多服務(wù)、比如同一個(gè)接口會(huì)調(diào)用Mysql、MQ、Redis等其他三方組件和服務(wù)。
圖片
因此需要確定是不是訪問Redis服務(wù)變慢進(jìn)而拖慢了整個(gè)服務(wù)的響應(yīng)變慢,那就是先自查!??
- ? 應(yīng)用服務(wù)訪問Redis的請(qǐng)求,記錄下每次請(qǐng)求的響應(yīng)延時(shí),對(duì)比是否響應(yīng)變長
- ? 是否其他節(jié)點(diǎn)存在同樣問題
假設(shè)我們確定了是Redis這條鏈路的問題?。ㄈ绻皇荝edis問題,文章就寫不下去啦??!哈哈),這里同樣存在兩種可能 ??
- ? 業(yè)務(wù)端請(qǐng)求到Redis服務(wù)網(wǎng)絡(luò)是否存在問題,存在網(wǎng)絡(luò)延遲情況
- ? Redis服務(wù)端本身出現(xiàn)問題,那需要進(jìn)一步排查
redis命令執(zhí)行過程-簡(jiǎn)約版.png
正常來說網(wǎng)絡(luò)存在問題的可能性還是比較小的,因?yàn)槿绻嬖诰W(wǎng)絡(luò)問題,那么其他服務(wù)同樣都會(huì)發(fā)生網(wǎng)絡(luò)延遲情況,如果你想了解網(wǎng)絡(luò)對(duì) Redis 性能的影響,可以用 iPerf 這樣的工具,測(cè)量從 Redis 客戶端到服務(wù)器端的網(wǎng)絡(luò)延遲,如果這個(gè)延遲有幾十毫秒甚至是幾百毫秒,就說明,Redis 運(yùn)行的網(wǎng)絡(luò)環(huán)境中很可能有大流量的其他應(yīng)用程序在運(yùn)行。
好,現(xiàn)在就剩下確定請(qǐng)求Redis的服務(wù)響應(yīng)耗時(shí)變長了,也是文章的要講的焦點(diǎn)問題,分析Redis變慢的原因,先查看Redis的響應(yīng)延遲,可以對(duì)Redis 進(jìn)行基準(zhǔn)性能測(cè)試。
基準(zhǔn)測(cè)試
基準(zhǔn)性能就是指 Redis 在一臺(tái)負(fù)載正常的機(jī)器上,其最大的響應(yīng)延遲和平均響應(yīng)延遲分別是多少
但是這又不能把別人或者官方的測(cè)試結(jié)果作為參考的指標(biāo),因?yàn)樵诓煌能浻布h(huán)境下,它的性能表現(xiàn)差別特別大,不同主頻型號(hào)的CPU、不同的SSD硬盤,都會(huì)極大影響Redis的性能表現(xiàn)。
那該以什么標(biāo)準(zhǔn)來認(rèn)定Redis變慢呢?
????一般來說,如果你觀察到的 Redis 運(yùn)行時(shí)延遲是其基線性能的 2 倍及以上,就可以認(rèn)定 Redis 變慢了
比如:執(zhí)行以下命令,就可以測(cè)試出這個(gè)實(shí)例 60 秒內(nèi)的最大響應(yīng)延遲
[root@VM-12-10-opencloudos ~]# redis-cli --intrinsic-latency 60
Max latency so far: 1 microseconds.
Max latency so far: 16 microseconds.
Max latency so far: 17 microseconds.
Max latency so far: 392 microseconds.
Max latency so far: 397 microseconds.
Max latency so far: 638 microseconds.
Max latency so far: 1869 microseconds.
Max latency so far: 2149 microseconds.
Max latency so far: 3026 microseconds.
Max latency so far: 6022 microseconds.
992399678 total runs (avg latency: 0.0605 microseconds / 60.46 nanoseconds per run).
Worst run took 99604x longer than the average latency.
可以看到,此時(shí)的基線性能已經(jīng)達(dá)到了 6.022ms,如果響應(yīng)延時(shí)為12ms,那么基本可以認(rèn)定為Redis變慢了,當(dāng)然我測(cè)試的機(jī)器性能比較差,你們可以用自己的機(jī)器試試
注意:這個(gè)命令只在Redis所在的服務(wù)器上運(yùn)行,避免網(wǎng)絡(luò)對(duì)基線性能的影響,只考慮服務(wù)端軟硬件環(huán)境的影響
到這里已經(jīng)確定了是Redis服務(wù)變慢,那么是哪里變慢了呢,接下來將進(jìn)行更詳細(xì)的說明
Redis性能影響要素
分析可能影響因素很重要,是判斷Redis性能的來源,如下圖:
圖片
在排除了網(wǎng)絡(luò)因素之后,可以歸納為Redis自身命令操作、文件系統(tǒng)和操作系統(tǒng)三個(gè)大因素可能導(dǎo)致Redis性能存在問題。
接下來的文章將圍繞這幾個(gè)要素出發(fā)排查和解決性能影響問題
Redis性能問題分析
慢日志分析
圖片
日志是個(gè)好東西,分析Mysql是否變慢我們可以通過查看慢日志的,同樣的分析Redis慢,同樣可以先看是否也存在慢日志 slowlog,這是基礎(chǔ)和直觀的方式。
Redis 提供的慢日志命令的統(tǒng)計(jì)功能,記錄了有哪些命令在執(zhí)行時(shí)耗時(shí)比較長,快速定位問題。
需要配置的參數(shù):
- ? slowlog-log-slower-than 配置對(duì)執(zhí)行時(shí)間大于多少微秒(microsecond, 1秒=10^6微秒) 的命令進(jìn)行記錄。線上可以設(shè)置為1000微秒,也就是1毫秒。
- ? slowlog-max-len 設(shè)置最大考驗(yàn)記錄多少條記錄。slow log 本身是一個(gè)先進(jìn)先出(FIFO) 隊(duì)列,當(dāng)隊(duì)列大小超過該配置的值時(shí),最舊的一條日志將被刪除。線上可以設(shè)置為1000以上。
配置如下:
//命令執(zhí)行耗時(shí)超過 10 毫秒,記錄慢日志
CONFIG SET slowlog-log-slower-than 10000
//只保留最近 500 條慢日志
CONFIG SET slowlog-max-len 500
我們看下慢日志如何查詢
127.0.0.1:6379> SLOWLOG get 3
1) (integer) 32693 # 慢日志ID
2) (integer) 1593763337 # 執(zhí)行時(shí)間戳
3) (integer) 5299 # 執(zhí)行耗時(shí)(微秒)
4) 1) "LRANGE" # 具體執(zhí)行的命令和參數(shù)
2) "user_list:2000"
3) "0"
4) "-1
注意慢日志功能比較粗糙簡(jiǎn)單,沒有持久化記錄能力,都是記錄在內(nèi)存中,沒有持久化到文件中,所以一般都是設(shè)置保留有限的慢命令條數(shù),如果慢命令比較多,會(huì)存在不能全部記錄的情況
常見集中導(dǎo)致Redis變慢不合理的命令使用方式:
- ? 獲取Redis中的key時(shí),避免使用keys *
- ? 高頻使用了 O(N) 及以上復(fù)雜度的命令,例如:SUNION、SORT、ZUNIONSTORE、ZINTERSTORE 聚合類命令
- ? O(N) 復(fù)雜度的命令,但 N 的值非常大,比如:hgetall、smembers、lrange、zrange等命令
這種情況下我們可以將復(fù)雜的聚合放在業(yè)務(wù)端處理,并且每次盡量少獲取大量數(shù)據(jù)
BigKey問題
圖片
分析慢日志時(shí)發(fā)現(xiàn)很多請(qǐng)求并不是復(fù)雜度高的命令,都是一些del、set、hset等的低復(fù)雜度命令,那么就要評(píng)估是否寫入了大key,也就是BigKey。
Bigkey 是指當(dāng) Redis 的字符串類型過大,非字符串類型元素過多 (hash,list,set等存儲(chǔ)中value值過多)
bigkey會(huì)帶來如下問題:
圖片
此時(shí)我們可以回想和檢查業(yè)務(wù)代碼,查看是否存在寫入bigkey的情況,評(píng)估好單個(gè)key的數(shù)據(jù)大小,避免存在過大數(shù)據(jù)。
除了代碼自查之外,可以使用命令查,如下:
[root@VM-12-10-opencloudos ~]# redis-cli -h 127.0.0.1 -p 6379 --bigkeys
-------- summary -------
Sampled 15 keys in the keyspace!
Total key length in bytes is 162 (avg len 10.80)
//最大的數(shù)據(jù)值
Biggest string found 'page4:20230921' has 12304 bytes
Biggest list found 'article:100' has 8 items
Biggest set found 'union:65:67' has 7 members
Biggest hash found 'page2:20230921' has 2 fields
Biggest zset found 'likeTopList' has 2 members
//平均值
6 strings with 24778 bytes (40.00% of keys, avg size 4129.67)
1 lists with 8 items (06.67% of keys, avg size 8.00)
6 sets with 24 members (40.00% of keys, avg size 4.00)
1 hashs with 2 fields (06.67% of keys, avg size 2.00)
1 zsets with 2 members (06.67% of keys, avg size 2.00)
--i 參數(shù),降低掃描的執(zhí)行速度,比如 --i 0.1 表示 100 毫秒執(zhí)行一次,降低掃描過程中對(duì) Redis運(yùn)行實(shí)例的影響。
--bigkeys命令原理解析
Redis 在內(nèi)部執(zhí)行了 SCAN 命令,遍歷整個(gè)實(shí)例中所有的 key 然后針對(duì) key 的類型,分別執(zhí)行 STRLEN、LLEN、HLEN、SCARD、ZCARD 命令,來獲取 String 類型的長度、容器類型(List、Hash、Set、ZSet)的元素個(gè)數(shù)
面對(duì)bigkey問題,我們可以這些方面下手去處理:
- 1. 對(duì)大Key進(jìn)行拆分
- 2. 優(yōu)化使用刪除Key的命令,可使用異步刪除 unlink 命令刪除緩存
- 3. 盡量不寫入大Key
- 4. 合理使用批處理命令
key集中過期
不知道大家是否遇到過,在某個(gè)時(shí)間點(diǎn)Redis突然出現(xiàn)一波延時(shí),而且報(bào)慢的,有時(shí)候超時(shí)還有時(shí)間規(guī)律
如果出現(xiàn)這種情況,就需要考慮是否存在大量key集中過期的情況,因?yàn)榇罅康膋ey在某個(gè)固定時(shí)間點(diǎn)集中過期,在這個(gè)時(shí)間點(diǎn)訪問Redis時(shí),就有可能導(dǎo)致延遲增加。
圖片
如果出現(xiàn)了這種情況,那么需要從兩個(gè)方面排查一下:
- ? 業(yè)務(wù)邏輯是否有定時(shí)任務(wù)的腳本程序,定期操作key
- ? Redis的Key數(shù)量出現(xiàn)集中過期清理
程序?qū)用孢@個(gè)我們自己排查就好了,這里主要看下為什么Key數(shù)量集中過期,集中過期為啥造成了Redis訪問變慢
Redis的Key過期策略是怎樣的?
被動(dòng)過期:只有應(yīng)用發(fā)起訪問某個(gè)key 時(shí),才判斷這個(gè)key是否已過期,如果已過期,則從Redis中刪除
主動(dòng)過期:在Redis 內(nèi)部維護(hù)了一個(gè)定時(shí)任務(wù),默認(rèn)每隔 100 毫秒(1秒10次)從全局的過期哈希表中隨機(jī)取出 20 個(gè) key,判斷然后刪除其中過期的 key,如果過期 key 的比例超過了 25%,則繼續(xù)重復(fù)此過程,直到過期 key 的比例下降到 25% 以下,或者這次任務(wù)的執(zhí)行耗時(shí)超過了 25 毫秒,才會(huì)退出循環(huán)
注意,這個(gè)主動(dòng)過期 key 的定時(shí)任務(wù),是在 Redis 主線程中執(zhí)行的
這也是我們主要關(guān)注的問題 【主動(dòng)過期清理】,那為什么會(huì)導(dǎo)致Redis延時(shí)呢?
因?yàn)橹鲃?dòng)過期是在Redis 主線程中執(zhí)行的,也就意味著會(huì)阻塞正常的請(qǐng)求命令。
進(jìn)一步說就是如果在執(zhí)行主動(dòng)過期的過程中,出現(xiàn)了需要大量刪除過期 key 的請(qǐng)求,那么此時(shí)應(yīng)用程序在訪問 Redis 時(shí),必須要等待這個(gè)過期任務(wù)執(zhí)行結(jié)束,Redis 才可以繼續(xù)處理新請(qǐng)求,這也就是為什么此時(shí)訪問Redis會(huì)突然出現(xiàn)延遲。
即使刪除過期key是耗時(shí)的,也不會(huì)記錄在slowlog慢日志中哦!
這里大家估計(jì)又有疑惑了,這不是慢了嗎?
別急,這是因?yàn)閟lowlog記錄的是Redis服務(wù)端在命令執(zhí)行前后計(jì)算每條命令的執(zhí)行時(shí)長,而過期清理的時(shí)候Redis是登錄狀態(tài),還不能處理客戶端發(fā)過來的請(qǐng)求,也就是在命令執(zhí)行之前進(jìn)行的。
這種情況我們可以這樣處理:
- ? 業(yè)務(wù)Key設(shè)置過期時(shí)間時(shí),加上一個(gè)隨機(jī)過期時(shí)間段,比如1分鐘
- ? 通過執(zhí)行info命令獲取過期Key數(shù)量【expired_keys】的統(tǒng)計(jì)值
- ? Redis 4.0以上版本,開啟 lazy-free 機(jī)制,把釋放內(nèi)存的操作放到后臺(tái)線程中執(zhí)行,避免阻塞主線程
預(yù)估內(nèi)存不足
我們知道服務(wù)器的內(nèi)存是有限的,這個(gè)是既定事實(shí),而且使用Redis時(shí)都會(huì)配置當(dāng)前實(shí)例可用的最大內(nèi)存maxmemory和數(shù)據(jù)自動(dòng)淘汰策略
maxmemory : 默認(rèn)為0 不限制。
//獲取maxmemory配置的大小
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "0" //默認(rèn)值是0
//可以在redis.conf中配置
maxmemory 1024mb
當(dāng)使用的內(nèi)存達(dá)到了 maxmemory 后,即使配置了自動(dòng)淘汰策略,仍然會(huì)在之后每次寫入新數(shù)據(jù)時(shí),操作延遲都會(huì)變長。
原因在于,當(dāng) Redis 內(nèi)存達(dá)到 maxmemory 后,每次寫入新的數(shù)據(jù)之前,Redis 必須先從實(shí)例中踢出一部分?jǐn)?shù)據(jù),讓整個(gè)實(shí)例的內(nèi)存維持在 maxmemory 之下,然后才能把新數(shù)據(jù)寫進(jìn)來。
圖片
Redis的常用 allkeys-lru / volatile-lru 的淘汰策略
volatile-lru :利用LRU算法移除設(shè)置過過期時(shí)間的key
allkeys-lru :利用LRU算法移除任何key (和上一個(gè)相比,刪除的key包括設(shè)置過期時(shí)間和不設(shè)置過期時(shí)間的)
Redis采用近似LRU算法,實(shí)現(xiàn)邏輯是什么樣的?
1:每次從實(shí)例中隨機(jī)選擇一個(gè)key (樣本集),并從樣本集中挑選最長時(shí)間未使用的 key 淘汰,剩下的放入待淘汰池
2:再次隨機(jī)獲取一批樣本集,并與第一步池子的key比較,進(jìn)而進(jìn)行淘汰最少訪問的key,剩下的放入待淘汰池
3:循環(huán)往復(fù)上面兩個(gè)操作步驟,直到實(shí)例內(nèi)存降到maxmemory值為止
假如我們淘汰策略刪除的是 bigkey,那么耗時(shí)還更久,可想而知 bigkey對(duì)Redis的危害應(yīng)該很大
不過針對(duì)內(nèi)存不足問題,我們也可以進(jìn)行一個(gè)優(yōu)化措施:
1:避免存儲(chǔ) bigkey,降低釋放內(nèi)存的耗時(shí)
2:合理預(yù)估內(nèi)存占用,避免達(dá)到內(nèi)存的使用上限
- ? 根據(jù)寫入Key的類型、數(shù)量及平均大小計(jì)算預(yù)估
- ? 寫入一小部分比例的真實(shí)業(yè)務(wù)數(shù)據(jù),然后進(jìn)行預(yù)估
3:Redis 4.0 及以上版本,開啟 layz-free 機(jī)制,把淘汰 key 釋放內(nèi)存的操作放到后臺(tái)線程中執(zhí)行
實(shí)際請(qǐng)求量超預(yù)期
一個(gè)系統(tǒng)處理請(qǐng)求是有上限的。Redis雖然處理速度很快,但是也有上限。因此在流量暴增的時(shí)候,會(huì)比較快達(dá)到Redis的處理瓶頸,這個(gè)時(shí)候整個(gè)系統(tǒng)也會(huì)變慢,出現(xiàn)slowlog等。
這個(gè)現(xiàn)象也比較好觀察,可以看看實(shí)例的cpu情況,如果持續(xù)100%,基本可以判定達(dá)到處理上限了。
這種情況最好我們要結(jié)合云監(jiān)控,對(duì)CPU使用率、訪問的QPS進(jìn)行監(jiān)控,發(fā)現(xiàn)系統(tǒng)瓶頸,看是否進(jìn)行擴(kuò)容和調(diào)整。
ok,關(guān)于Redis變慢問題的上半部分就分享到這里了,下期將繼續(xù)更新其他可能導(dǎo)致Redis變慢的情況。