萬(wàn)字總結(jié),Redis 性能問(wèn)題排查解決手冊(cè)!
公眾號(hào)關(guān)注“杰哥的IT之旅”,
選擇“星標(biāo)”,重磅干貨,第一時(shí)間送達(dá)!

性能相關(guān)的數(shù)據(jù)指標(biāo)
通過(guò)Redis-cli命令行界面訪問(wèn)到Redis服務(wù)器,然后使用info命令獲取所有與Redis服務(wù)相關(guān)的信息。通過(guò)這些信息來(lái)分析文章后面提到的一些性能指標(biāo)。

info命令輸出的數(shù)據(jù)可分為10個(gè)類(lèi)別,分別是:
1、server
2、clients
3、memory
4、persistence
5、stats
6、replication
7、cpu
8、commandstats
9、cluster
10、keyspace
這篇主要介紹比較重要的2部分性能指標(biāo)memory和stats。
需要注意的是info命令返回的信息,并沒(méi)有命令響應(yīng)延遲相關(guān)的數(shù)據(jù)信息,所以后面會(huì)詳細(xì)介紹怎么獲取與延遲相關(guān)的數(shù)據(jù)指標(biāo)。
倘若你覺(jué)得info輸出的信息太多并且雜亂無(wú)章,可以指定info命令的參數(shù)來(lái)獲取單個(gè)分類(lèi)下的數(shù)據(jù)。比如輸入info memory命令,會(huì)只返回與內(nèi)存相關(guān)的數(shù)據(jù)。

為了快速定位并解決性能問(wèn)題,這里選擇5個(gè)關(guān)鍵性的數(shù)據(jù)指標(biāo),它包含了大多數(shù)人在使用Redis上會(huì)經(jīng)常碰到的性能問(wèn)題。
內(nèi)存使用率used_memory
上圖中used_memory 字段數(shù)據(jù)表示的是:由Redis分配器分配的內(nèi)存總量,以字節(jié)(byte)為單位。其中used_memory_human上的數(shù)據(jù)和used_memory是一樣的值,它以M為單位顯示,僅為了方便閱讀。

used_memory是Redis使用的內(nèi)存總量,它包含了實(shí)際緩存占用的內(nèi)存和Redis自身運(yùn)行所占用的內(nèi)存(如元數(shù)據(jù)、lua)。它是由Redis使用內(nèi)存分配器分配的內(nèi)存,所以這個(gè)數(shù)據(jù)并沒(méi)有把內(nèi)存碎片浪費(fèi)掉的內(nèi)存給統(tǒng)計(jì)進(jìn)去。
其他字段代表的含義,都以字節(jié)為單位:
1、used_memory_rss:從操作系統(tǒng)上顯示已經(jīng)分配的內(nèi)存總量。
2、mem_fragmentation_ratio:內(nèi)存碎片率。
3、used_memory_lua:Lua腳本引擎所使用的內(nèi)存大小。
mem_allocator:在編譯時(shí)指定的Redis使用的內(nèi)存分配器,可以是libc、jemalloc、tcmalloc。
因內(nèi)存交換引起的性能問(wèn)題
內(nèi)存使用率是Redis服務(wù)最關(guān)鍵的一部分。如果一個(gè)Redis實(shí)例的內(nèi)存使用率超過(guò)可用最大內(nèi)存 (used_memory > 可用最大內(nèi)存),那么操作系統(tǒng)開(kāi)始進(jìn)行內(nèi)存與swap空間交換,把內(nèi)存中舊的或不再使用的內(nèi)容寫(xiě)入硬盤(pán)上(硬盤(pán)上的這塊空間叫Swap分區(qū)),以便騰出新的物理內(nèi)存給新頁(yè)或活動(dòng)頁(yè)(page)使用。
在硬盤(pán)上進(jìn)行讀寫(xiě)操作要比在內(nèi)存上進(jìn)行讀寫(xiě)操作,時(shí)間上慢了近5個(gè)數(shù)量級(jí),內(nèi)存是0.1μs單位、而硬盤(pán)是10ms。如果Redis進(jìn)程上發(fā)生內(nèi)存交換,那么Redis和依賴(lài)Redis上數(shù)據(jù)的應(yīng)用會(huì)受到嚴(yán)重的性能影響。通過(guò)查看used_memory指標(biāo)可知道Redis正在使用的內(nèi)存情況,如果used_memory>可用最大內(nèi)存,那就說(shuō)明Redis實(shí)例正在進(jìn)行內(nèi)存交換或者已經(jīng)內(nèi)存交換完畢。管理員根據(jù)這個(gè)情況,執(zhí)行相對(duì)應(yīng)的應(yīng)急措施。
跟蹤內(nèi)存使用率
若是在使用Redis期間沒(méi)有開(kāi)啟rdb快照或aof持久化策略,那么緩存數(shù)據(jù)在Redis崩潰時(shí)就有丟失的危險(xiǎn)。因?yàn)楫?dāng)Redis內(nèi)存使用率超過(guò)可用內(nèi)存的95%時(shí),部分?jǐn)?shù)據(jù)開(kāi)始在內(nèi)存與swap空間來(lái)回交換,這時(shí)就可能有丟失數(shù)據(jù)的危險(xiǎn)。
當(dāng)開(kāi)啟并觸發(fā)快照功能時(shí),Redis會(huì)fork一個(gè)子進(jìn)程把當(dāng)前內(nèi)存中的數(shù)據(jù)完全復(fù)制一份寫(xiě)入到硬盤(pán)上。因此若是當(dāng)前使用內(nèi)存超過(guò)可用內(nèi)存的45%時(shí)觸發(fā)快照功能,那么此時(shí)進(jìn)行的內(nèi)存交換會(huì)變的非常危險(xiǎn)(可能會(huì)丟失數(shù)據(jù))。倘若在這個(gè)時(shí)候?qū)嵗嫌写罅款l繁的更新操作,問(wèn)題會(huì)變得更加嚴(yán)重。
通過(guò)減少Redis的內(nèi)存占用率,來(lái)避免這樣的問(wèn)題,或者使用下面的技巧來(lái)避免內(nèi)存交換發(fā)生:
1、假如緩存數(shù)據(jù)小于4GB,就使用32位的Redis實(shí)例。因?yàn)?2位實(shí)例上的指針大小只有64位的一半,它的內(nèi)存空間占用空間會(huì)更少些。這有一個(gè)壞處就是,假設(shè)物理內(nèi)存超過(guò)4GB,那么32位實(shí)例能使用的內(nèi)存仍然會(huì)被限制在4GB以下。要是實(shí)例同時(shí)也共享給其他一些應(yīng)用使用的話,那可能需要更高效的64位Redis實(shí)例,這種情況下切換到32位是不可取的。不管使用哪種方式,Redis的dump文件在32位和64位之間是互相兼容的, 因此倘若有減少占用內(nèi)存空間的需求,可以嘗試先使用32位,后面再切換到64位上。
2、盡可能的使用Hash數(shù)據(jù)結(jié)構(gòu)。因?yàn)镽edis在儲(chǔ)存小于100個(gè)字段的Hash結(jié)構(gòu)上,其存儲(chǔ)效率是非常高的。所以在不需要集合(set)操作或list的push/pop操作的時(shí)候,盡可能的使用Hash結(jié)構(gòu)。比如,在一個(gè)web應(yīng)用程序中,需要存儲(chǔ)一個(gè)對(duì)象表示用戶(hù)信息,使用單個(gè)key表示一個(gè)用戶(hù),其每個(gè)屬性存儲(chǔ)在Hash的字段里,這樣要比給每個(gè)屬性單獨(dú)設(shè)置一個(gè)key-value要高效的多。通常情況下倘若有數(shù)據(jù)使用string結(jié)構(gòu),用多個(gè)key存儲(chǔ)時(shí),那么應(yīng)該轉(zhuǎn)換成單key多字段的Hash結(jié)構(gòu)。如上述例子中介紹的Hash結(jié)構(gòu)應(yīng)包含,單個(gè)對(duì)象的屬性或者單個(gè)用戶(hù)各種各樣的資料。Hash結(jié)構(gòu)的操作命令是HSET(key, fields, value)和HGET(key, field),使用它可以存儲(chǔ)或從Hash中取出指定的字段。
3、設(shè)置key的過(guò)期時(shí)間。一個(gè)減少內(nèi)存使用率的簡(jiǎn)單方法就是,每當(dāng)存儲(chǔ)對(duì)象時(shí)確保設(shè)置key的過(guò)期時(shí)間。倘若key在明確的時(shí)間周期內(nèi)使用或者舊key不大可能被使用時(shí),就可以用Redis過(guò)期時(shí)間命令(expire,expireat, pexpire, pexpireat)去設(shè)置過(guò)期時(shí)間,這樣Redis會(huì)在key過(guò)期時(shí)自動(dòng)刪除key。假如你知道每秒鐘有多少個(gè)新key-value被創(chuàng)建,那可以調(diào)整key的存活時(shí)間,并指定閥值去限制Redis使用的最大內(nèi)存。
4、回收key。在Redis配置文件中(一般叫Redis.conf),通過(guò)設(shè)置“maxmemory”屬性的值可以限制Redis最大使用的內(nèi)存,修改后重啟實(shí)例生效。也可以使用客戶(hù)端命令config set maxmemory 去修改值,這個(gè)命令是立即生效的,但會(huì)在重啟后會(huì)失效,需要使用config rewrite命令去刷新配置文件。若是啟用了Redis快照功能,應(yīng)該設(shè)置“maxmemory”值為系統(tǒng)可使用內(nèi)存的45%,因?yàn)榭煺諘r(shí)需要一倍的內(nèi)存來(lái)復(fù)制整個(gè)數(shù)據(jù)集,也就是說(shuō)如果當(dāng)前已使用45%,在快照期間會(huì)變成95%(45%+45%+5%),其中5%是預(yù)留給其他的開(kāi)銷(xiāo)。如果沒(méi)開(kāi)啟快照功能,maxmemory最高能設(shè)置為系統(tǒng)可用內(nèi)存的95%。
當(dāng)內(nèi)存使用達(dá)到設(shè)置的最大閥值時(shí),需要選擇一種key的回收策略,可在Redis.conf配置文件中修改“maxmemory-policy”屬性值。若是Redis數(shù)據(jù)集中的key都設(shè)置了過(guò)期時(shí)間,那么“volatile-ttl”策略是比較好的選擇。但如果key在達(dá)到最大內(nèi)存限制時(shí)沒(méi)能夠迅速過(guò)期,或者根本沒(méi)有設(shè)置過(guò)期時(shí)間。那么設(shè)置為“allkeys-lru”值比較合適,它允許Redis從整個(gè)數(shù)據(jù)集中挑選最近最少使用的key進(jìn)行刪除(LRU淘汰算法)。Redis還提供了一些其他淘汰策略,如下:
1、volatile-lru:使用LRU算法從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集合中淘汰數(shù)據(jù)。
2、volatile-ttl:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集合中挑選即將過(guò)期的數(shù)據(jù)淘汰。
3、volatile-random:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集合中隨機(jī)挑選數(shù)據(jù)淘汰。
4、allkeys-lru:使用LRU算法從所有數(shù)據(jù)集合中淘汰數(shù)據(jù)。
5、allkeys-random:從數(shù)據(jù)集合中任意選擇數(shù)據(jù)淘汰
6、no-enviction:禁止淘汰數(shù)據(jù)。
通過(guò)設(shè)置maxmemory為系統(tǒng)可用內(nèi)存的45%或95%(取決于持久化策略)和設(shè)置“maxmemory-policy”為“volatile-ttl”或“allkeys-lru”(取決于過(guò)期設(shè)置),可以比較準(zhǔn)確的限制Redis最大內(nèi)存使用率,在絕大多數(shù)場(chǎng)景下使用這2種方式可確保Redis不會(huì)進(jìn)行內(nèi)存交換。倘若你擔(dān)心由于限制了內(nèi)存使用率導(dǎo)致丟失數(shù)據(jù)的話,可以設(shè)置noneviction值禁止淘汰數(shù)據(jù)。
命令處理數(shù)total_commands_processed
在info信息里的total_commands_processed字段顯示了Redis服務(wù)處理命令的總數(shù),其命令都是從一個(gè)或多個(gè)Redis客戶(hù)端請(qǐng)求過(guò)來(lái)的。Redis每時(shí)每刻都在處理從客戶(hù)端請(qǐng)求過(guò)來(lái)的命令,它可以是Redis提供的140種命令的任意一個(gè)。total_commands_processed字段的值是遞增的,比如Redis服務(wù)分別處理了client_x請(qǐng)求過(guò)來(lái)的2個(gè)命令和client_y請(qǐng)求過(guò)來(lái)的3個(gè)命令,那么命令處理總數(shù)(total_commands_processed)就會(huì)加上5。

分析命令處理總數(shù),診斷響應(yīng)延遲。
在Redis實(shí)例中,跟蹤命令處理總數(shù)是解決響應(yīng)延遲問(wèn)題最關(guān)鍵的部分,因?yàn)镽edis是個(gè)單線程模型,客戶(hù)端過(guò)來(lái)的命令是按照順序執(zhí)行的。比較常見(jiàn)的延遲是帶寬,通過(guò)千兆網(wǎng)卡的延遲大約有200μs。倘若明顯看到命令的響應(yīng)時(shí)間變慢,延遲高于200μs,那可能是Redis命令隊(duì)列里等待處理的命令數(shù)量比較多。如上所述,延遲時(shí)間增加導(dǎo)致響應(yīng)時(shí)間變慢可能是由于一個(gè)或多個(gè)慢命令引起的,這時(shí)可以看到每秒命令處理數(shù)在明顯下降,甚至于后面的命令完全被阻塞,導(dǎo)致Redis性能降低。要分析解決這個(gè)性能問(wèn)題,需要跟蹤命令處理數(shù)的數(shù)量和延遲時(shí)間。
比如可以寫(xiě)個(gè)腳本,定期記錄total_commands_processed的值。當(dāng)客戶(hù)端明顯發(fā)現(xiàn)響應(yīng)時(shí)間過(guò)慢時(shí),可以通過(guò)記錄的total_commands_processed歷史數(shù)據(jù)值來(lái)判斷命理處理總數(shù)是上升趨勢(shì)還是下降趨勢(shì),以便排查問(wèn)題。
使用命令處理總數(shù)解決延遲時(shí)間增加。
通過(guò)與記錄的歷史數(shù)據(jù)比較得知,命令處理總數(shù)確實(shí)是處于上升或下降狀態(tài),那么可能是有2個(gè)原因引起的:
1、命令隊(duì)列里的命令數(shù)量過(guò)多,后面命令一直在等待中。
2、幾個(gè)慢命令阻塞Redis。
下面有三個(gè)辦法可以解決,因上面2條原因引起的響應(yīng)延遲問(wèn)題。
1、使用多參數(shù)命令:若是客戶(hù)端在很短的時(shí)間內(nèi)發(fā)送大量的命令過(guò)來(lái),會(huì)發(fā)現(xiàn)響應(yīng)時(shí)間明顯變慢,這由于后面命令一直在等待隊(duì)列中前面大量命令執(zhí)行完畢。有個(gè)方法可以改善延遲問(wèn)題,就是通過(guò)單命令多參數(shù)的形式取代多命令單參數(shù)的形式。舉例來(lái)說(shuō),循環(huán)使用LSET命令去添加1000個(gè)元素到list結(jié)構(gòu)中,是性能比較差的一種方式,更好的做法是在客戶(hù)端創(chuàng)建一個(gè)1000元素的列表,用單個(gè)命令LPUSH或RPUSH,通過(guò)多參數(shù)構(gòu)造形式一次性把1000個(gè)元素發(fā)送的Redis服務(wù)上。下面的表格是Redis的一些操作命令,有單個(gè)參數(shù)命令和支持多個(gè)參數(shù)的命令,通過(guò)這些命令可盡量減少使用多命令的次數(shù)。

2、管道命令:另一個(gè)減少多命令的方法是使用管道(pipeline),把幾個(gè)命令合并一起執(zhí)行,從而減少因網(wǎng)絡(luò)開(kāi)銷(xiāo)引起的延遲問(wèn)題。因?yàn)?0個(gè)命令單獨(dú)發(fā)送到服務(wù)端會(huì)引起10次網(wǎng)絡(luò)延遲開(kāi)銷(xiāo),使用管道會(huì)一次性把執(zhí)行結(jié)果返回,僅需要一次網(wǎng)絡(luò)延遲開(kāi)銷(xiāo)。Redis本身支持管道命令,大多數(shù)客戶(hù)端也支持,倘若當(dāng)前實(shí)例延遲很明顯,那么使用管道去降低延遲是非常有效的。
3、避免操作大集合的慢命令:如果命令處理頻率過(guò)低導(dǎo)致延遲時(shí)間增加,這可能是因?yàn)槭褂昧烁邥r(shí)間復(fù)雜度的命令操作導(dǎo)致,這意味著每個(gè)命令從集合中獲取數(shù)據(jù)的時(shí)間增大。所以減少使用高時(shí)間復(fù)雜的命令,能顯著的提高的Redis的性能。下面的表格是高時(shí)間復(fù)雜度命令的列表,其詳細(xì)描述了命令的屬性,有這助于高效合理的、最優(yōu)化的使用這些命令(如果不得不使用的話),以提高Redis性能。

延遲時(shí)間
Redis的延遲數(shù)據(jù)是無(wú)法從info信息中獲取的。倘若想要查看延遲時(shí)間,可以用 Redis-cli工具加--latency參數(shù)運(yùn)行,如:
Redis-cli --latency -h 127.0.0.1 -p 6379
其host和port是Redis實(shí)例的ip及端口。由于當(dāng)前服務(wù)器不同的運(yùn)行情況,延遲時(shí)間可能有所誤差,通常1G網(wǎng)卡的延遲時(shí)間是200μs。
以毫秒為單位測(cè)量Redis的響應(yīng)延遲時(shí)間,樓主本機(jī)的延遲是300μs:

跟蹤Redis延遲性能
Redis之所以這么流行的主要原因之一就是低延遲特性帶來(lái)的高性能,所以說(shuō)解決延遲問(wèn)題是提高Redis性能最直接的辦法。拿1G帶寬來(lái)說(shuō),若是延遲時(shí)間遠(yuǎn)高于200μs,那明顯是出現(xiàn)了性能問(wèn)題。雖然在服務(wù)器上會(huì)有一些慢的IO操作,但Redis是單核接受所有客戶(hù)端的請(qǐng)求,所有請(qǐng)求是按良好的順序排隊(duì)執(zhí)行。因此若是一個(gè)客戶(hù)端發(fā)過(guò)來(lái)的命令是個(gè)慢操作,那么其他所有請(qǐng)求必須等待它完成后才能繼續(xù)執(zhí)行。
使用延遲命令提高性能
一旦確定延遲時(shí)間是個(gè)性能問(wèn)題后,這里有幾個(gè)辦法可以用來(lái)分析解決性能問(wèn)題。
1. 使用slowlog查出引發(fā)延遲的慢命令:Redis中的slowlog命令可以讓我們快速定位到那些超出指定執(zhí)行時(shí)間的慢命令,默認(rèn)情況下命令若是執(zhí)行時(shí)間超過(guò)10ms就會(huì)被記錄到日志。slowlog只會(huì)記錄其命令執(zhí)行的時(shí)間,不包含io往返操作,也不記錄單由網(wǎng)絡(luò)延遲引起的響應(yīng)慢。通常1gb帶寬的網(wǎng)絡(luò)延遲,預(yù)期在200μs左右,倘若一個(gè)命令僅執(zhí)行時(shí)間就超過(guò)10ms,那比網(wǎng)絡(luò)延遲慢了近50倍。想要查看所有執(zhí)行時(shí)間比較慢的命令,可以通過(guò)使用Redis-cli工具,輸入slowlog get命令查看,返回結(jié)果的第三個(gè)字段以微妙位單位顯示命令的執(zhí)行時(shí)間。假如只需要查看最后10個(gè)慢命令,輸入slowlog get 10即可。關(guān)于怎么定位到是由慢命令引起的延遲問(wèn)題,可查看total_commands_processed介紹章節(jié)。

圖中字段分別意思是:
1、1=日志的唯一標(biāo)識(shí)符
2、2=被記錄命令的執(zhí)行時(shí)間點(diǎn),以 UNIX 時(shí)間戳格式表示
3、3=查詢(xún)執(zhí)行時(shí)間,以微秒為單位。例子中命令使用54毫秒。
4、4=執(zhí)行的命令,以數(shù)組的形式排列。完整命令是config get *。
倘若你想自定義慢命令的標(biāo)準(zhǔn),可以調(diào)整觸發(fā)日志記錄慢命令的閥值。若是很少或沒(méi)有命令超過(guò)10ms,想降低記錄的閥值,比如5毫秒,可在Redis-cli工具中輸入下面的命令配置:
config set slowlog-log-slower-than 5000
也可以在Redis.config配置文件中設(shè)置,以微妙位單位。
2.監(jiān)控客戶(hù)端的連接:因?yàn)镽edis是單線程模型(只能使用單核),來(lái)處理所有客戶(hù)端的請(qǐng)求, 但由于客戶(hù)端連接數(shù)的增長(zhǎng),處理請(qǐng)求的線程資源開(kāi)始降低分配給單個(gè)客戶(hù)端連接的處理時(shí)間,這時(shí)每個(gè)客戶(hù)端需要花費(fèi)更多的時(shí)間去等待Redis共享服務(wù)的響應(yīng)。這種情況下監(jiān)控客戶(hù)端連接數(shù)是非常重要的,因?yàn)榭蛻?hù)端創(chuàng)建連接數(shù)的數(shù)量可能超出預(yù)期的數(shù)量,也可能是客戶(hù)端端沒(méi)有有效的釋放連接。在Redis-cli工具中輸入info clients可以查看到當(dāng)前實(shí)例的所有客戶(hù)端連接信息。如下圖,第一個(gè)字段(connected_clients)顯示當(dāng)前實(shí)例客戶(hù)端連接的總數(shù):

Redis默認(rèn)允許客戶(hù)端連接的最大數(shù)量是10000。若是看到連接數(shù)超過(guò)5000以上,那可能會(huì)影響Redis的性能。倘若一些或大部分客戶(hù)端發(fā)送大量的命令過(guò)來(lái),這個(gè)數(shù)字會(huì)低的多。
3.限制客戶(hù)端連接數(shù):自Redis2.6以后,允許使用者在配置文件(Redis.conf)maxclients屬性上修改客戶(hù)端連接的最大數(shù),也可以通過(guò)在Redis-cli工具上輸入config set maxclients 去設(shè)置最大連接數(shù)。根據(jù)連接數(shù)負(fù)載的情況,這個(gè)數(shù)字應(yīng)該設(shè)置為預(yù)期連接數(shù)峰值的110%到150之間,若是連接數(shù)超出這個(gè)數(shù)字后,Redis會(huì)拒絕并立刻關(guān)閉新來(lái)的連接。通過(guò)設(shè)置最大連接數(shù)來(lái)限制非預(yù)期數(shù)量的連接數(shù)增長(zhǎng),是非常重要的。另外,新連接嘗試失敗會(huì)返回一個(gè)錯(cuò)誤消息,這可以讓客戶(hù)端知道,Redis此時(shí)有非預(yù)期數(shù)量的連接數(shù),以便執(zhí)行對(duì)應(yīng)的處理措施。上述二種做法對(duì)控制連接數(shù)的數(shù)量和持續(xù)保持Redis的性能最優(yōu)是非常重要的,
4.加強(qiáng)內(nèi)存管理:較少的內(nèi)存會(huì)引起Redis延遲時(shí)間增加。如果Redis占用內(nèi)存超出系統(tǒng)可用內(nèi)存,操作系統(tǒng)會(huì)把Redis進(jìn)程的一部分?jǐn)?shù)據(jù),從物理內(nèi)存交換到硬盤(pán)上,內(nèi)存交換會(huì)明顯的增加延遲時(shí)間。關(guān)于怎么監(jiān)控和減少內(nèi)存使用,可查看used_memory介紹章節(jié)。
5. 性能數(shù)據(jù)指標(biāo):分析解決Redis性能問(wèn)題,通常需要把延遲時(shí)間的數(shù)據(jù)變化與其他性能指標(biāo)的變化相關(guān)聯(lián)起來(lái)。命令處理總數(shù)下降的發(fā)生可能是由慢命令阻塞了整個(gè)系統(tǒng),但如果命令處理總數(shù)的增加,同時(shí)內(nèi)存使用率也增加,那么就可能是由于內(nèi)存交換引起的性能問(wèn)題。對(duì)于這種性能指標(biāo)相關(guān)聯(lián)的分析,需要從歷史數(shù)據(jù)上來(lái)觀察到數(shù)據(jù)指標(biāo)的重要變化,此外還可以觀察到單個(gè)性能指標(biāo)相關(guān)聯(lián)的所有其他性能指標(biāo)信息。這些數(shù)據(jù)可以在Redis上收集,周期性的調(diào)用內(nèi)容為Redis info的腳本,然后分析輸出的信息,記錄到日志文件中。當(dāng)延遲發(fā)生變化時(shí),用日志文件配合其他數(shù)據(jù)指標(biāo),把數(shù)據(jù)串聯(lián)起來(lái)排查定位問(wèn)題。
內(nèi)存碎片率
info信息中的mem_fragmentation_ratio給出了內(nèi)存碎片率的數(shù)據(jù)指標(biāo),它是由操系統(tǒng)分配的內(nèi)存除以Redis分配的內(nèi)存得出:

used_memory和used_memory_rss數(shù)字都包含的內(nèi)存分配有:
1、用戶(hù)定義的數(shù)據(jù):內(nèi)存被用來(lái)存儲(chǔ)key-value值。
2、內(nèi)部開(kāi)銷(xiāo):存儲(chǔ)內(nèi)部Redis信息用來(lái)表示不同的數(shù)據(jù)類(lèi)型。
used_memory_rss的rss是Resident Set Size的縮寫(xiě),表示該進(jìn)程所占物理內(nèi)存的大小,是操作系統(tǒng)分配給Redis實(shí)例的內(nèi)存大小。除了用戶(hù)定義的數(shù)據(jù)和內(nèi)部開(kāi)銷(xiāo)以外,used_memory_rss指標(biāo)還包含了內(nèi)存碎片的開(kāi)銷(xiāo),內(nèi)存碎片是由操作系統(tǒng)低效的分配/回收物理內(nèi)存導(dǎo)致的。
操作系統(tǒng)負(fù)責(zé)分配物理內(nèi)存給各個(gè)應(yīng)用進(jìn)程,Redis使用的內(nèi)存與物理內(nèi)存的映射是由操作系統(tǒng)上虛擬內(nèi)存管理分配器完成的。
舉個(gè)例子來(lái)說(shuō),Redis需要分配連續(xù)內(nèi)存塊來(lái)存儲(chǔ)1G的數(shù)據(jù)集,這樣的話更有利,但可能物理內(nèi)存上沒(méi)有超過(guò)1G的連續(xù)內(nèi)存塊,那操作系統(tǒng)就不得不使用多個(gè)不連續(xù)的小內(nèi)存塊來(lái)分配并存儲(chǔ)這1G數(shù)據(jù),也就導(dǎo)致內(nèi)存碎片的產(chǎn)生。
內(nèi)存分配器另一個(gè)復(fù)雜的層面是,它經(jīng)常會(huì)預(yù)先分配一些內(nèi)存塊給引用,這樣做會(huì)使加快應(yīng)用程序的運(yùn)行。
理解資源性能
跟蹤內(nèi)存碎片率對(duì)理解Redis實(shí)例的資源性能是非常重要的。內(nèi)存碎片率稍大于1是合理的,這個(gè)值表示內(nèi)存碎片率比較低,也說(shuō)明redis沒(méi)有發(fā)生內(nèi)存交換。但如果內(nèi)存碎片率超過(guò)1.5,那就說(shuō)明Redis消耗了實(shí)際需要物理內(nèi)存的150%,其中50%是內(nèi)存碎片率。若是內(nèi)存碎片率低于1的話,說(shuō)明Redis內(nèi)存分配超出了物理內(nèi)存,操作系統(tǒng)正在進(jìn)行內(nèi)存交換。內(nèi)存交換會(huì)引起非常明顯的響應(yīng)延遲,可查看used_memory介紹章節(jié)。

上圖中的0.99即99%。
用內(nèi)存碎片率預(yù)測(cè)性能問(wèn)題
倘若內(nèi)存碎片率超過(guò)了1.5,那可能是操作系統(tǒng)或Redis實(shí)例中內(nèi)存管理變差的表現(xiàn)。下面有3種方法解決內(nèi)存管理變差的問(wèn)題,并提高Redis性能:
1. 重啟Redis服務(wù)器:如果內(nèi)存碎片率超過(guò)1.5,重啟Redis服務(wù)器可以讓額外產(chǎn)生的內(nèi)存碎片失效并重新作為新內(nèi)存來(lái)使用,使操作系統(tǒng)恢復(fù)高效的內(nèi)存管理。額外碎片的產(chǎn)生是由于Redis釋放了內(nèi)存塊,但內(nèi)存分配器并沒(méi)有返回內(nèi)存給操作系統(tǒng),這個(gè)內(nèi)存分配器是在編譯時(shí)指定的,可以是libc、jemalloc或者tcmalloc。通過(guò)比較used_memory_peak, used_memory_rss和used_memory_metrics的數(shù)據(jù)指標(biāo)值可以檢查額外內(nèi)存碎片的占用。從名字上可以看出,used_memory_peak是過(guò)去Redis內(nèi)存使用的峰值,而不是當(dāng)前使用內(nèi)存的值。如果used_memory_peak和used_memory_rss的值大致上相等,而且二者明顯超過(guò)了used_memory值,這說(shuō)明額外的內(nèi)存碎片正在產(chǎn)生。在Redis-cli工具上輸入info memory可以查看上面三個(gè)指標(biāo)的信息:

在重啟服務(wù)器之前,需要在Redis-cli工具上輸入shutdown save命令,意思是強(qiáng)制讓Redis數(shù)據(jù)庫(kù)執(zhí)行保存操作并關(guān)閉Redis服務(wù),這樣做能保證在執(zhí)行Redis關(guān)閉時(shí)不丟失任何數(shù)據(jù)。在重啟后,Redis會(huì)從硬盤(pán)上加載持久化的文件,以確保數(shù)據(jù)集持續(xù)可用。
2.限制內(nèi)存交換:如果內(nèi)存碎片率低于1,Redis實(shí)例可能會(huì)把部分?jǐn)?shù)據(jù)交換到硬盤(pán)上。內(nèi)存交換會(huì)嚴(yán)重影響Redis的性能,所以應(yīng)該增加可用物理內(nèi)存或減少實(shí)Redis內(nèi)存占用。可查看used_memory章節(jié)的優(yōu)化建議。
3.修改內(nèi)存分配器:Redis支持glibc’s malloc、jemalloc11、tcmalloc幾種不同的內(nèi)存分配器,每個(gè)分配器在內(nèi)存分配和碎片上都有不同的實(shí)現(xiàn)。不建議普通管理員修改Redis默認(rèn)內(nèi)存分配器,因?yàn)檫@需要完全理解這幾種內(nèi)存分配器的差異,也要重新編譯Redis。這個(gè)方法更多的是讓其了解Redis內(nèi)存分配器所做的工作,當(dāng)然也是改善內(nèi)存碎片問(wèn)題的一種辦法。
回收key
info信息中的evicted_keys字段顯示的是,因?yàn)閙axmemory限制導(dǎo)致key被回收刪除的數(shù)量。關(guān)于maxmemory的介紹見(jiàn)前面章節(jié),回收key的情況只會(huì)發(fā)生在設(shè)置maxmemory值后,不設(shè)置會(huì)發(fā)生內(nèi)存交換。當(dāng)Redis由于內(nèi)存壓力需要回收一個(gè)key時(shí),Redis首先考慮的不是回收最舊的數(shù)據(jù),而是在最近最少使用的key或即將過(guò)期的key中隨機(jī)選擇一個(gè)key,從數(shù)據(jù)集中刪除。
這可以在配置文件中設(shè)置maxmemory-policy值為“volatile-lru”或“volatile-ttl”,來(lái)確定Redis是使用lru策略還是過(guò)期時(shí)間策略。倘若所有的key都有明確的過(guò)期時(shí)間,那過(guò)期時(shí)間回收策略是比較合適的。若是沒(méi)有設(shè)置key的過(guò)期時(shí)間或者說(shuō)沒(méi)有足夠的過(guò)期key,那設(shè)置lru策略是比較合理的,這可以回收key而不用考慮其過(guò)期狀態(tài)。

根據(jù)key回收定位性能問(wèn)題
跟蹤key回收是非常重要的,因?yàn)橥ㄟ^(guò)回收key,可以保證合理分配Redis有限的內(nèi)存資源。如果evicted_keys值經(jīng)常超過(guò)0,那應(yīng)該會(huì)看到客戶(hù)端命令響應(yīng)延遲時(shí)間增加,因?yàn)镽edis不但要處理客戶(hù)端過(guò)來(lái)的命令請(qǐng)求,還要頻繁的回收滿(mǎn)足條件的key。
需要注意的是,回收key對(duì)性能的影響遠(yuǎn)沒(méi)有內(nèi)存交換嚴(yán)重,若是在強(qiáng)制內(nèi)存交換和設(shè)置回收策略做一個(gè)選擇的話,選擇設(shè)置回收策略是比較合理的,因?yàn)榘褍?nèi)存數(shù)據(jù)交換到硬盤(pán)上對(duì)性能影響非常大。
減少回收key以提升性能
減少回收key的數(shù)量是提升Redis性能的直接辦法,下面有2種方法可以減少回收key的數(shù)量:
1.增加內(nèi)存限制:倘若開(kāi)啟快照功能,maxmemory需要設(shè)置成物理內(nèi)存的45%,這幾乎不會(huì)有引發(fā)內(nèi)存交換的危險(xiǎn)。若是沒(méi)有開(kāi)啟快照功能,設(shè)置系統(tǒng)可用內(nèi)存的95%是比較合理的,具體參考前面的快照和maxmemory限制章節(jié)。如果maxmemory的設(shè)置是低于45%或95%(視持久化策略),通過(guò)增加maxmemory的值能讓Redis在內(nèi)存中存儲(chǔ)更多的key,這能顯著減少回收key的數(shù)量。若是maxmemory已經(jīng)設(shè)置為推薦的閥值后,增加maxmemory限制不但無(wú)法提升性能,反而會(huì)引發(fā)內(nèi)存交換,導(dǎo)致延遲增加、性能降低。maxmemory的值可以在Redis-cli工具上輸入config set maxmemory命令來(lái)設(shè)置。
需要注意的是,這個(gè)設(shè)置是立即生效的,但重啟后丟失,需要永久化保存的話,再輸入config rewrite命令會(huì)把內(nèi)存中的新配置刷新到配置文件中。
2.對(duì)實(shí)例進(jìn)行分片:分片是把數(shù)據(jù)分割成合適大小,分別存放在不同的Redis實(shí)例上,每一個(gè)實(shí)例都包含整個(gè)數(shù)據(jù)集的一部分。通過(guò)分片可以把很多服務(wù)器聯(lián)合起來(lái)存儲(chǔ)數(shù)據(jù),相當(dāng)于增加總的物理內(nèi)存,使其在沒(méi)有內(nèi)存交換和回收key的策略下也能存儲(chǔ)更多的key。假如有一個(gè)非常大的數(shù)據(jù)集,maxmemory已經(jīng)設(shè)置,實(shí)際內(nèi)存使用也已經(jīng)超過(guò)了推薦設(shè)置的閥值,那通過(guò)數(shù)據(jù)分片能明顯減少key的回收,從而提高Redis的性能。分片的實(shí)現(xiàn)有很多種方法,下面是Redis實(shí)現(xiàn)分片的幾種常見(jiàn)方式:
a. Hash分片:一個(gè)比較簡(jiǎn)單的方法實(shí)現(xiàn),通過(guò)Hash函數(shù)計(jì)算出key的Hash值,然后值所在范圍對(duì)應(yīng)特定的Redis實(shí)例。
b. 代理分片:客戶(hù)端把請(qǐng)求發(fā)送到代理上,代理通過(guò)分片配置表選擇對(duì)應(yīng)的Redis實(shí)例。如Twitter的Twemproxy,豌豆莢的codis。
c. 一致性Hash分片
d. 虛擬桶分片
總結(jié)
對(duì)于開(kāi)發(fā)者來(lái)說(shuō),Redis是個(gè)速度非常快的key-value內(nèi)存數(shù)據(jù)庫(kù),并提供了方便的API接口。為了最好最優(yōu)的使用Redis,需要理解哪些因素能影響到Redis性能,哪些數(shù)據(jù)指標(biāo)能幫助我們避免性能陷阱。通過(guò)本篇,能理解Redis中的重要性能指標(biāo),怎么查看,更重要的是怎么利用這些數(shù)據(jù)排查解決Redis性能問(wèn)題。
本篇文章還翻譯了一電子書(shū)的中間15頁(yè),電子書(shū)可以在公眾號(hào)后臺(tái)回復(fù):Redis性能 獲取。
來(lái)源:https://www.cnblogs.com/mushroom/p/4738170.html
推薦閱讀
學(xué) Redis,至少要看看這篇!7000 字小結(jié)

