本篇文章給大家?guī)?lái)了關(guān)于Redis的相關(guān)知識(shí),其中主要介紹了關(guān)于Redis變慢的原因及排查方法的相關(guān)問(wèn)題,下面一起來(lái)看一下,希望對(duì)大家有幫助。
推薦學(xué)習(xí):Redis視頻教程
原因1:實(shí)例內(nèi)存達(dá)到上限
排查思路
如果你的 Redis 實(shí)例設(shè)置了內(nèi)存上限 maxmemory,那么也有可能導(dǎo)致 Redis 變慢。
當(dāng)我們把 Redis 當(dāng)做純緩存使用時(shí),通常會(huì)給這個(gè)實(shí)例設(shè)置一個(gè)內(nèi)存上限 maxmemory,然后設(shè)置一個(gè)數(shù)據(jù)淘汰策略。而當(dāng)實(shí)例的內(nèi)存達(dá)到了 maxmemory 后,你可能會(huì)發(fā)現(xiàn),在此之后每次寫(xiě)入新數(shù)據(jù),操作延遲變大了。
導(dǎo)致變慢的原因
當(dāng) Redis 內(nèi)存達(dá)到 maxmemory 后,每次寫(xiě)入新的數(shù)據(jù)之前,Redis 必須先從實(shí)例中踢出一部分?jǐn)?shù)據(jù),讓整個(gè)實(shí)例的內(nèi)存維持在 maxmemory 之下,然后才能把新數(shù)據(jù)寫(xiě)進(jìn)來(lái)。
這個(gè)踢出舊數(shù)據(jù)的邏輯也是需要消耗時(shí)間的,而具體耗時(shí)的長(zhǎng)短,要取決于你配置的淘汰策略:
- allkeys-lru:不管 key 是否設(shè)置了過(guò)期,淘汰最近最少訪問(wèn)的 key
- volatile-lru:只淘汰最近最少訪問(wèn)、并設(shè)置了過(guò)期時(shí)間的 key
- allkeys-random:不管 key 是否設(shè)置了過(guò)期,隨機(jī)淘汰 key
- volatile-random:只隨機(jī)淘汰設(shè)置了過(guò)期時(shí)間的 key
- allkeys-ttl:不管 key 是否設(shè)置了過(guò)期,淘汰即將過(guò)期的 key
- noeviction:不淘汰任何 key,實(shí)例內(nèi)存達(dá)到 maxmeory 后,再寫(xiě)入新數(shù)據(jù)直接返回錯(cuò)誤
- allkeys-lfu:不管 key 是否設(shè)置了過(guò)期,淘汰訪問(wèn)頻率最低的 key(4.0+版本支持)
- volatile-lfu:只淘汰訪問(wèn)頻率最低、并設(shè)置了過(guò)期時(shí)間 key(4.0+版本支持)
具體使用哪種策略,我們需要根據(jù)具體的業(yè)務(wù)場(chǎng)景來(lái)配置。一般最常使用的是 allkeys-lru / volatile-lru 淘汰策略,它們的處理邏輯是,每次從實(shí)例中隨機(jī)取出一批 key(這個(gè)數(shù)量可配置),然后淘汰一個(gè)最少訪問(wèn)的 key,之后把剩下的 key 暫存到一個(gè)池子中,繼續(xù)隨機(jī)取一批 key,并與之前池子中的 key 比較,再淘汰一個(gè)最少訪問(wèn)的 key。以此往復(fù),直到實(shí)例內(nèi)存降到 maxmemory 之下。
需要注意的是,Redis 的淘汰數(shù)據(jù)的邏輯與刪除過(guò)期 key 的一樣,也是在命令真正執(zhí)行之前執(zhí)行的,也就是說(shuō)它也會(huì)增加我們操作 Redis 的延遲,而且,寫(xiě) OPS 越高,延遲也會(huì)越明顯。
另外,如果此時(shí)你的 Redis 實(shí)例中還存儲(chǔ)了 bigkey,那么在淘汰刪除 bigkey 釋放內(nèi)存時(shí),也會(huì)耗時(shí)比較久。
看到了么?bigkey 的危害到處都是,這也是前面我提醒你盡量不存儲(chǔ) bigkey 的原因。
解決方案
- 避免存儲(chǔ) bigkey,降低釋放內(nèi)存的耗時(shí)
- 淘汰策略改為隨機(jī)淘汰,隨機(jī)淘汰比 LRU 要快很多(視業(yè)務(wù)情況調(diào)整)
- 拆分實(shí)例,把淘汰 key 的壓力分?jǐn)偟蕉鄠€(gè)實(shí)例上
- 如果使用的是 Redis 4.0 以上版本,開(kāi)啟 layz-free 機(jī)制,把淘汰 key 釋放內(nèi)存的操作放到后臺(tái)線程中執(zhí)行(配置 lazyfree-lazy-eviction = yes)
原因2:開(kāi)啟內(nèi)存大頁(yè)
排查思路
- 我們都知道,應(yīng)用程序向操作系統(tǒng)申請(qǐng)內(nèi)存時(shí),是按內(nèi)存頁(yè)進(jìn)行申請(qǐng)的,而常規(guī)的內(nèi)存頁(yè)大小是 4KB。
- Linux 內(nèi)核從 2.6.38 開(kāi)始,支持了內(nèi)存大頁(yè)機(jī)制,該機(jī)制允許應(yīng)用程序以 2MB 大小為單位,向操作系統(tǒng)申請(qǐng)內(nèi)存。
- 應(yīng)用程序每次向操作系統(tǒng)申請(qǐng)的內(nèi)存單位變大了,但這也意味著申請(qǐng)內(nèi)存的耗時(shí)變長(zhǎng)。
導(dǎo)致變慢的原因
- 當(dāng) Redis 在執(zhí)行后臺(tái) RDB 和 AOF rewrite 時(shí),采用 fork 子進(jìn)程的方式來(lái)處理。但主進(jìn)程 fork 子進(jìn)程后,此時(shí)的主進(jìn)程依舊是可以接收寫(xiě)請(qǐng)求的,而進(jìn)來(lái)的寫(xiě)請(qǐng)求,會(huì)采用 Copy On Write(寫(xiě)時(shí)復(fù)制)的方式操作內(nèi)存數(shù)據(jù)。
- 也就是說(shuō),主進(jìn)程一旦有數(shù)據(jù)需要修改,Redis 并不會(huì)直接修改現(xiàn)有內(nèi)存中的數(shù)據(jù),而是先將這塊內(nèi)存數(shù)據(jù)拷貝出來(lái),再修改這塊新內(nèi)存的數(shù)據(jù),這就是所謂的「寫(xiě)時(shí)復(fù)制」。
- 寫(xiě)時(shí)復(fù)制你也可以理解成,誰(shuí)需要發(fā)生寫(xiě)操作,誰(shuí)就需要先拷貝,再修改。
- 這樣做的好處是,父進(jìn)程有任何寫(xiě)操作,并不會(huì)影響子進(jìn)程的數(shù)據(jù)持久化(子進(jìn)程只持久化 fork 這一瞬間整個(gè)實(shí)例中的所有數(shù)據(jù)即可,不關(guān)心新的數(shù)據(jù)變更,因?yàn)樽舆M(jìn)程只需要一份內(nèi)存快照,然后持久化到磁盤(pán)上)。
- 但是請(qǐng)注意,主進(jìn)程在拷貝內(nèi)存數(shù)據(jù)時(shí),這個(gè)階段就涉及到新內(nèi)存的申請(qǐng),如果此時(shí)操作系統(tǒng)開(kāi)啟了內(nèi)存大頁(yè),那么在此期間,客戶端即便只修改 10B 的數(shù)據(jù),Redis 在申請(qǐng)內(nèi)存時(shí)也會(huì)以 2MB 為單位向操作系統(tǒng)申請(qǐng),申請(qǐng)內(nèi)存的耗時(shí)變長(zhǎng),進(jìn)而導(dǎo)致每個(gè)寫(xiě)請(qǐng)求的延遲增加,影響到 Redis 性能。
- 同樣地,如果這個(gè)寫(xiě)請(qǐng)求操作的是一個(gè) bigkey,那主進(jìn)程在拷貝這個(gè) bigkey 內(nèi)存塊時(shí),一次申請(qǐng)的內(nèi)存會(huì)更大,時(shí)間也會(huì)更久??梢?jiàn),bigkey 在這里又一次影響到了性能。
解決方案
關(guān)閉內(nèi)存大頁(yè)機(jī)制。
首先,你需要查看 Redis 機(jī)器是否開(kāi)啟了內(nèi)存大頁(yè):
$ cat /sys/kernel/mm/transparent_hugepage/enabled [always] madvise never
如果輸出選項(xiàng)是 always,就表示目前開(kāi)啟了內(nèi)存大頁(yè)機(jī)制,我們需要關(guān)掉它:
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
其實(shí),操作系統(tǒng)提供的內(nèi)存大頁(yè)機(jī)制,其優(yōu)勢(shì)是,可以在一定程序上降低應(yīng)用程序申請(qǐng)內(nèi)存的次數(shù)。
但是對(duì)于 Redis 這種對(duì)性能和延遲極其敏感的數(shù)據(jù)庫(kù)來(lái)說(shuō),我們希望 Redis 在每次申請(qǐng)內(nèi)存時(shí),耗時(shí)盡量短,所以我不建議你在 Redis 機(jī)器上開(kāi)啟這個(gè)機(jī)制。
原因3:使用Swap
排查思路
如果你發(fā)現(xiàn) Redis 突然變得非常慢,每次的操作耗時(shí)都達(dá)到了幾百毫秒甚至秒級(jí),那此時(shí)你就需要檢查 Redis 是否使用到了 Swap,在這種情況下 Redis 基本上已經(jīng)無(wú)法提供高性能的服務(wù)了。
導(dǎo)致變慢的原因
什么是 Swap?為什么使用 Swap 會(huì)導(dǎo)致 Redis 的性能下降?
如果你對(duì)操作系統(tǒng)有些了解,就會(huì)知道操作系統(tǒng)為了緩解內(nèi)存不足對(duì)應(yīng)用程序的影響,允許把一部分內(nèi)存中的數(shù)據(jù)換到磁盤(pán)上,以達(dá)到應(yīng)用程序?qū)?nèi)存使用的緩沖,這些內(nèi)存數(shù)據(jù)被換到磁盤(pán)上的區(qū)域,就是 Swap。
問(wèn)題就在于,當(dāng)內(nèi)存中的數(shù)據(jù)被換到磁盤(pán)上后,Redis 再訪問(wèn)這些數(shù)據(jù)時(shí),就需要從磁盤(pán)上讀取,訪問(wèn)磁盤(pán)的速度要比訪問(wèn)內(nèi)存慢幾百倍!尤其是針對(duì) Redis 這種對(duì)性能要求極高、性能極其敏感的數(shù)據(jù)庫(kù)來(lái)說(shuō),這個(gè)操作延時(shí)是無(wú)法接受的。
此時(shí),你需要檢查 Redis 機(jī)器的內(nèi)存使用情況,確認(rèn)是否存在使用了 Swap。你可以通過(guò)以下方式來(lái)查看 Redis 進(jìn)程是否使用到了 Swap:
# 先找到 Redis 的進(jìn)程 ID $ ps -aux | grep redis-server # 查看 Redis Swap 使用情況 $ cat /proc/$pid/smaps | egrep '^(Swap|Size)'
輸出結(jié)果如下
Size: 1256 kB Swap: 0 kB Size: 4 kB Swap: 0 kB Size: 132 kB Swap: 0 kB Size: 63488 kB Swap: 0 kB Size: 132 kB Swap: 0 kB Size: 65404 kB Swap: 0 kB Size: 1921024 kB Swap: 0 kB ...
這個(gè)結(jié)果會(huì)列出 Redis 進(jìn)程的內(nèi)存使用情況。
每一行 Size 表示 Redis 所用的一塊內(nèi)存大小,Size 下面的 Swap 就表示這塊 Size 大小的內(nèi)存,有多少數(shù)據(jù)已經(jīng)被換到磁盤(pán)上了,如果這兩個(gè)值相等,說(shuō)明這塊內(nèi)存的數(shù)據(jù)都已經(jīng)完全被換到磁盤(pán)上了。
如果只是少量數(shù)據(jù)被換到磁盤(pán)上,例如每一塊 Swap 占對(duì)應(yīng) Size 的比例很小,那影響并不是很大。如果是幾百兆甚至上 GB 的內(nèi)存被換到了磁盤(pán)上,那么你就需要警惕了,這種情況 Redis 的性能肯定會(huì)急劇下降。
解決方案
- 增加機(jī)器的內(nèi)存,讓 Redis 有足夠的內(nèi)存可以使用
- 整理內(nèi)存空間,釋放出足夠的內(nèi)存供 Redis 使用,然后釋放 Redis 的 Swap,讓 Redis 重新使用內(nèi)存
釋放 Redis 的 Swap 過(guò)程通常要重啟實(shí)例,為了避免重啟實(shí)例對(duì)業(yè)務(wù)的影響,一般會(huì)先進(jìn)行主從切換,然后釋放舊主節(jié)點(diǎn)的 Swap,重啟舊主節(jié)點(diǎn)實(shí)例,待從庫(kù)數(shù)據(jù)同步完成后,再進(jìn)行主從切換即可。
可見(jiàn),當(dāng) Redis 使用到 Swap 后,此時(shí)的 Redis 性能基本已達(dá)不到高性能的要求(你可以理解為武功被廢),所以你也需要提前預(yù)防這種情況。
預(yù)防的辦法就是,你需要對(duì) Redis 機(jī)器的內(nèi)存和 Swap 使用情況進(jìn)行監(jiān)控,在內(nèi)存不足或使用到 Swap 時(shí)報(bào)警出來(lái),及時(shí)處理。
原因4:網(wǎng)絡(luò)帶寬過(guò)載
排查思路
如果以上產(chǎn)生性能問(wèn)題的場(chǎng)景,你都規(guī)避掉了,而且 Redis 也穩(wěn)定運(yùn)行了很長(zhǎng)時(shí)間,但在某個(gè)時(shí)間點(diǎn)之后開(kāi)始,操作 Redis 突然開(kāi)始變慢了,而且一直持續(xù)下去,這種情況又是什么原因?qū)е拢?/p>
此時(shí)你需要排查一下 Redis 機(jī)器的網(wǎng)絡(luò)帶寬是否過(guò)載,是否存在某個(gè)實(shí)例把整個(gè)機(jī)器的網(wǎng)路帶寬占滿的情況。
導(dǎo)致變慢的原因
網(wǎng)絡(luò)帶寬過(guò)載的情況下,服務(wù)器在 TCP 層和網(wǎng)絡(luò)層就會(huì)出現(xiàn)數(shù)據(jù)包發(fā)送延遲、丟包等情況。
Redis 的高性能,除了操作內(nèi)存之外,就在于網(wǎng)絡(luò) IO 了,如果網(wǎng)絡(luò) IO 存在瓶頸,那么也會(huì)嚴(yán)重影響 Redis 的性能。
解決方案
- 及時(shí)確認(rèn)占滿網(wǎng)絡(luò)帶寬 Redis 實(shí)例,如果屬于正常的業(yè)務(wù)訪問(wèn),那就需要及時(shí)擴(kuò)容或遷移實(shí)例了,避免因?yàn)檫@個(gè)實(shí)例流量過(guò)大,影響這個(gè)機(jī)器的其他實(shí)例。
- 運(yùn)維層面,你需要對(duì) Redis 機(jī)器的各項(xiàng)指標(biāo)增加監(jiān)控,包括網(wǎng)絡(luò)流量,在網(wǎng)絡(luò)流量達(dá)到一定閾值時(shí)提前報(bào)警,及時(shí)確認(rèn)和擴(kuò)容。
原因5:其他原因
1) 頻繁短連接
你的業(yè)務(wù)應(yīng)用,應(yīng)該使用長(zhǎng)連接操作 Redis,避免頻繁的短連接。
頻繁的短連接會(huì)導(dǎo)致 Redis 大量時(shí)間耗費(fèi)在連接的建立和釋放上,TCP 的三次握手和四次揮手同樣也會(huì)增加訪問(wèn)延遲。
2) 運(yùn)維監(jiān)控
前面我也提到了,要想提前預(yù)知 Redis 變慢的情況發(fā)生,必不可少的就是做好完善的監(jiān)控。
監(jiān)控其實(shí)就是對(duì)采集 Redis 的各項(xiàng)運(yùn)行時(shí)指標(biāo),通常的做法是監(jiān)控程序定時(shí)采集 Redis 的 INFO 信息,然后根據(jù) INFO 信息中的狀態(tài)數(shù)據(jù)做數(shù)據(jù)展示和報(bào)警。
這里我需要提醒你的是,在寫(xiě)一些監(jiān)控腳本,或使用開(kāi)源的監(jiān)控組件時(shí),也不能掉以輕心。
在寫(xiě)監(jiān)控腳本訪問(wèn) Redis 時(shí),盡量采用長(zhǎng)連接的方式采集狀態(tài)信息,避免頻繁短連接。同時(shí),你還要注意控制訪問(wèn) Redis 的頻率,避免影響到業(yè)務(wù)請(qǐng)求。
在使用一些開(kāi)源的監(jiān)控組件時(shí),最好了解一下這些組件的實(shí)現(xiàn)原理,以及正確配置這些組件,防止出現(xiàn)監(jiān)控組件發(fā)生 Bug,導(dǎo)致短時(shí)大量操作 Redis,影響 Redis 性能的情況發(fā)生。
我們當(dāng)時(shí)就發(fā)生過(guò),DBA 在使用一些開(kāi)源組件時(shí),因?yàn)榕渲煤褪褂脝?wèn)題,導(dǎo)致監(jiān)控程序頻繁地與 Redis 建立和斷開(kāi)連接,導(dǎo)致 Redis 響應(yīng)變慢。
3)其它程序爭(zhēng)搶資源
最后需要提醒你的是,你的 Redis 機(jī)器最好專(zhuān)項(xiàng)專(zhuān)用,只用來(lái)部署 Redis 實(shí)例,不要部署其他應(yīng)用程序,盡量給 Redis 提供一個(gè)相對(duì)「安靜」的環(huán)境,避免其它程序占用 CPU、內(nèi)存、磁盤(pán)資源,導(dǎo)致分配給 Redis 的資源不足而受到影響。
推薦學(xué)習(xí):Redis視頻教程