Redis 問題排查解決手冊(值得收藏)
點擊上方藍色“小哈學Java”,選擇“設為星標” 回復“資源”獲取獨家整理的學習資料!
性能相關的數據指標
通過Redis-cli命令行界面訪問到Redis服務器,然后使用info命令獲取所有與Redis服務相關的信息。通過這些信息來分析文章后面提到的一些性能指標。

info命令輸出的數據可分為10個類別,分別是:
server clients memory persistence stats replication cpu commandstats cluster keyspace
這篇主要介紹比較重要的2部分性能指標memory和stats。
需要注意的是info命令返回的信息,并沒有命令響應延遲相關的數據信息,所以后面會詳細介紹怎么獲取與延遲相關的數據指標。
倘若你覺得info輸出的信息太多并且雜亂無章,可以指定info命令的參數來獲取單個分類下的數據。比如輸入info memory命令,會只返回與內存相關的數據。

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

used_memory是Redis使用的內存總量,它包含了實際緩存占用的內存和Redis自身運行所占用的內存(如元數據、lua)。它是由Redis使用內存分配器分配的內存,所以這個數據并沒有把內存碎片浪費掉的內存給統(tǒng)計進去。
其他字段代表的含義,都以字節(jié)為單位:
used_memory_rss:從操作系統(tǒng)上顯示已經分配的內存總量。 mem_fragmentation_ratio:內存碎片率。 used_memory_lua:Lua腳本引擎所使用的內存大小。 mem_allocator:在編譯時指定的Redis使用的內存分配器,可以是libc、jemalloc、tcmalloc。
因內存交換引起的性能問題
內存使用率是Redis服務最關鍵的一部分。如果一個Redis實例的內存使用率超過可用最大內存 (used_memory > 可用最大內存),那么操作系統(tǒng)開始進行內存與swap空間交換,把內存中舊的或不再使用的內容寫入硬盤上(硬盤上的這塊空間叫Swap分區(qū)),以便騰出新的物理內存給新頁或活動頁(page)使用。
在硬盤上進行讀寫操作要比在內存上進行讀寫操作,時間上慢了近5個數量級,內存是0.1μs單位、而硬盤是10ms。如果Redis進程上發(fā)生內存交換,那么Redis和依賴Redis上數據的應用會受到嚴重的性能影響。
通過查看used_memory指標可知道Redis正在使用的內存情況,如果used_memory>可用最大內存,那就說明Redis實例正在進行內存交換或者已經內存交換完畢。管理員根據這個情況,執(zhí)行相對應的應急措施。
跟蹤內存使用率
通過減少Redis的內存占用率,來避免這樣的問題,或者使用下面的技巧來避免內存交換發(fā)生:
volatile-lru:使用LRU算法從已設置過期時間的數據集合中淘汰數據。 volatile-ttl:從已設置過期時間的數據集合中挑選即將過期的數據淘汰。 volatile-random:從已設置過期時間的數據集合中隨機挑選數據淘汰。 allkeys-lru:使用LRU算法從所有數據集合中淘汰數據。 allkeys-random:從數據集合中任意選擇數據淘汰 no-enviction:禁止淘汰數據。
命令處理數total_commands_processed
在info信息里的total_commands_processed字段顯示了Redis服務處理命令的總數,其命令都是從一個或多個Redis客戶端請求過來的。Redis每時每刻都在處理從客戶端請求過來的命令,它可以是Redis提供的140種命令的任意一個。total_commands_processed字段的值是遞增的,比如Redis服務分別處理了client_x請求過來的2個命令和client_y請求過來的3個命令,那么命令處理總數(total_commands_processed)就會加上5。

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

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

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

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

圖中字段分別意思是:
1=日志的唯一標識符 2=被記錄命令的執(zhí)行時間點,以 UNIX 時間戳格式表示 3=查詢執(zhí)行時間,以微秒為單位。例子中命令使用54毫秒。 4= 執(zhí)行的命令,以數組的形式排列。完整命令是config get *。
倘若你想自定義慢命令的標準,可以調整觸發(fā)日志記錄慢命令的閥值。若是很少或沒有命令超過10ms,想降低記錄的閥值,比如5毫秒,可在Redis-cli工具中輸入下面的命令配置:
config set slowlog-log-slower-than 5000
也可以在Redis.config配置文件中設置,以微妙位單位。

Redis默認允許客戶端連接的最大數量是10000。若是看到連接數超過5000以上,那可能會影響Redis的性能。倘若一些或大部分客戶端發(fā)送大量的命令過來,這個數字會低的多。
內存碎片率
info信息中的mem_fragmentation_ratio給出了內存碎片率的數據指標,它是由操系統(tǒng)分配的內存除以Redis分配的內存得出:

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

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

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

根據key回收定位性能問題
跟蹤key回收是非常重要的,因為通過回收key,可以保證合理分配Redis有限的內存資源。如果evicted_keys值經常超過0,那應該會看到客戶端命令響應延遲時間增加,因為Redis不但要處理客戶端過來的命令請求,還要頻繁的回收滿足條件的key。
需要注意的是,回收key對性能的影響遠沒有內存交換嚴重,若是在強制內存交換和設置回收策略做一個選擇的話,選擇設置回收策略是比較合理的,因為把內存數據交換到硬盤上對性能影響非常大(見前面章節(jié))。
減少回收key以提升性能
減少回收key的數量是提升Redis性能的直接辦法,下面有2種方法可以減少回收key的數量:
1.增加內存限制:
倘若開啟快照功能,maxmemory需要設置成物理內存的45%,這幾乎不會有引發(fā)內存交換的危險。若是沒有開啟快照功能,設置系統(tǒng)可用內存的95%是比較合理的,具體參考前面的快照和maxmemory限制章節(jié)。如果maxmemory的設置是低于45%或95%(視持久化策略),通過增加maxmemory的值能讓Redis在內存中存儲更多的key,這能顯著減少回收key的數量。若是maxmemory已經設置為推薦的閥值后,增加maxmemory限制不但無法提升性能,反而會引發(fā)內存交換,導致延遲增加、性能降低。maxmemory的值可以在Redis-cli工具上輸入config set maxmemory命令來設置。
需要注意的是,這個設置是立即生效的,但重啟后丟失,需要永久化保存的話,再輸入config rewrite命令會把內存中的新配置刷新到配置文件中。
2.對實例進行分片:
分片是把數據分割成合適大小,分別存放在不同的Redis實例上,每一個實例都包含整個數據集的一部分。通過分片可以把很多服務器聯合起來存儲數據,相當于增加總的物理內存,使其在沒有內存交換和回收key的策略下也能存儲更多的key。假如有一個非常大的數據集,maxmemory已經設置,實際內存使用也已經超過了推薦設置的閥值,那通過數據分片能明顯減少key的回收,從而提高Redis的性能。分片的實現有很多種方法,下面是Redis實現分片的幾種常見方式:
a. Hash分片:一個比較簡單的方法實現,通過Hash函數計算出key的Hash值,然后值所在范圍對應特定的Redis實例。 b. 代理分片:客戶端把請求發(fā)送到代理上,代理通過分片配置表選擇對應的Redis實例。如Twitter的Twemproxy,豌豆莢的codis。 c. 一致性Hash分片 d. 虛擬桶分片
總結
對于開發(fā)者來說,Redis是個速度非常快的key-value內存數據庫,并提供了方便的API接口。
為了最好最優(yōu)的使用Redis,需要理解哪些因素能影響到Redis性能,哪些數據指標能幫助我們避免性能陷阱。通過本篇,能理解Redis中的重要性能指標,怎么查看,更重要的是怎么利用這些數據排查解決Redis性能問題。
本篇博客主要翻譯了一電子書的中間15頁,電子書地址是:
https://www.datadoghq.com/wp-content/uploads/2013/09/Understanding-the-Top-5-Redis-Performance-Metrics.pdf
樓主翻譯水平有限,如有錯誤之處請多多包涵,也歡迎指出交流,希望本文對大家有所幫助。
作者:蘑菇先生
出處:http://mushroom.cnblogs.com/
最近面試BAT,整理一份面試資料《Java面試BATJ通關手冊》,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數據庫、數據結構等等。
獲取方式:點“在看”,關注公眾號并回復 Java 領取,更多內容陸續(xù)奉上。
文章有幫助的話,在看,轉發(fā)吧。
謝謝支持喲 (*^__^*)


