本篇文章給大家帶來了關于redis的相關知識,遇到redis延遲過高可能會引發各種問題,下面我們就一起來分析一下如何確定redis有性能問題和解決方案,希望對大家有幫助。
推薦學習:redis
Redis?通常是我們業務系統中一個重要的組件,比如:緩存、賬號登錄信息、排行榜等。
一旦?Redis?請求延遲增加,可能就會導致業務系統“雪崩”。
我在單身紅娘婚戀類型互聯網公司工作,在雙十一推出下單就送女朋友的活動。
誰曾想,凌晨?12?點之后,用戶量暴增,出現了一個技術故障,用戶無法下單,當時老大火冒三丈!
經過查找發現?Redis?報?Could?not?get?a?resource?from?the?pool。
獲取不到連接資源,并且集群中的單臺?Redis?連接量很高。
大量的流量沒了?Redis?的緩存響應,直接打到了?MySQL,最后數據庫也宕機了……
于是各種更改最大連接數、連接等待數,雖然報錯信息頻率有所緩解,但還是持續報錯。
后來經過線下測試,發現存放?Redis?中的字符數據很大,平均?1s?返回數據。
可以發現,一旦?Redis?延遲過高,會引發各種問題。
今天跟大家一起來分析下如何確定?Redis?有性能問題和解決方案。
Redis?性能出問題了么?
最大延遲是客戶端發出命令到客戶端收到命令的響應的時間,正常情況下?Redis?處理的時間極短,在微秒級別。
當?Redis?出現性能波動的時候,比如達到幾秒到十幾秒,這個很明顯我們可以認定?Redis?性能變慢了。
有的硬件配置比較高,當延遲?0.6ms,我們可能就認定變慢了。硬件比較差的可能?3?ms?我們才認為出現問題。
那我們該如何定義?Redis?真的變慢了呢?
所以,我們需要對當前環境的?Redis?基線性能做測量,也就是在一個系統在低壓力、無干擾情況下的基本性能。
當你發現?Redis?運行時時的延遲是基線性能的?2?倍以上,就可以判定?Redis?性能變慢了。
延遲基線測量
redis-cli?命令提供了–intrinsic-latency?選項,用來監測和統計測試期間內的最大延遲(以毫秒為單位),這個延遲可以作為?Redis?的基線性能。
redis-cli?--latency?-h?`host`?-p?`port`
比如執行如下指令:
redis-cli?--intrinsic-latency?100 Max?latency?so?far:?4?microseconds. Max?latency?so?far:?18?microseconds. Max?latency?so?far:?41?microseconds. Max?latency?so?far:?57?microseconds. Max?latency?so?far:?78?microseconds. Max?latency?so?far:?170?microseconds. Max?latency?so?far:?342?microseconds. Max?latency?so?far:?3079?microseconds. 45026981?total?runs?(avg?latency:?2.2209?microseconds?/?2220.89?nanoseconds?per?run). Worst?run?took?1386x?longer?than?the?average?latency.
注意:參數100是測試將執行的秒數。我們運行測試的時間越長,我們就越有可能發現延遲峰值。
通常運行?100?秒通常是合適的,足以發現延遲問題了,當然我們可以選擇不同時間運行幾次,避免誤差。
運行的最大延遲是?3079?微秒,所以基線性能是?3079?(3?毫秒)微秒。
需要注意的是,我們要在?Redis?的服務端運行,而不是客戶端。這樣,可以避免網絡對基線性能的影響。
可以通過?-h?host?-p?port?來連接服務端,如果想監測網絡對?Redis?的性能影響,可以使用?Iperf?測量客戶端到服務端的網絡延遲。
如果網絡延遲幾百毫秒,說明網絡可能有其他大流量的程序在運行導致網絡擁塞,需要找運維協調網絡的流量分配。
慢指令監控
如何判斷是否是慢指令呢?
看操作復雜度是否是O(N)。官方文檔對每個命令的復雜度都有介紹,盡可能使用O(1)?和?O(log?N)命令。
涉及到集合操作的復雜度一般為O(N),比如集合全量查詢HGETALL、SMEMBERS,以及集合的聚合操作:SORT、LREM、?SUNION等。
有監控數據可以觀測呢?代碼不是我寫的,不知道有沒有人用了慢指令。
有兩種方式可以排查到:
-
使用?Redis?慢日志功能查出慢命令;
-
latency-monitor(延遲監控)工具。
此外,可以使用自己(top、htop、prstat?等)快速檢查?Redis?主進程的?CPU?消耗。如果?CPU?使用率很高而流量不高,通常表明使用了慢速命令。
慢日志功能
Redis?中的?slowlog?命令可以讓我們快速定位到那些超出指定執行時間的慢命令,默認情況下命令若是執行時間超過?10ms?就會被記錄到日志。
slowlog?只會記錄其命令執行的時間,不包含?io?往返操作,也不記錄單由網絡延遲引起的響應慢。
我們可以根據基線性能來自定義慢命令的標準(配置成基線性能最大延遲的?2?倍),調整觸發記錄慢命令的閾值。
可以在?redis-cli?中輸入以下命令配置記錄?6?毫秒以上的指令:
redis-cli?CONFIG?SET?slowlog-log-slower-than?6000
也可以在?Redis.config?配置文件中設置,以微秒為單位。
想要查看所有執行時間比較慢的命令,可以通過使用?Redis-cli?工具,輸入?slowlog?get?命令查看,返回結果的第三個字段以微秒位單位顯示命令的執行時間。
假如只需要查看最后?2?個慢命令,輸入?slowlog?get?2?即可。
示例:獲取最近2個慢查詢命令
127.0.0.1:6381>?SLOWLOG?get?2 1)?1)?(integer)?6 ???2)?(integer)?1458734263 ???3)?(integer)?74372 ???4)?1)?"hgetall" ??????2)?"max.dsp.blacklist" 2)?1)?(integer)?5 ???2)?(integer)?1458734258 ???3)?(integer)?5411075 ???4)?1)?"keys" ??????2)?"max.dsp.blacklist"
以第一個?HGET?命令為例分析,每個?slowlog?實體共?4?個字段:
-
字段?1:1?個整數,表示這個?slowlog?出現的序號,server?啟動后遞增,當前為?6。
-
字段?2:表示查詢執行時的?Unix?時間戳。
-
字段?3:表示查詢執行微秒數,當前是?74372?微秒,約?74ms。
-
字段?4:?表示查詢的命令和參數,如果參數很多或很大,只會顯示部分參數個數。當前命令是hgetall?max.dsp.blacklist。
Latency?Monitoring
Redis?在?2.8.13?版本引入了?Latency?Monitoring?功能,用于以秒為粒度監控各種事件的發生頻率。
啟用延遲監視器的第一步是設置延遲閾值(單位毫秒)。只有超過該閾值的時間才會被記錄,比如我們根據基線性能(3ms)的?3?倍設置閾值為?9?ms。
可以用?redis-cli?設置也可以在?Redis.config?中設置;
CONFIG?SET?latency-monitor-threshold?9
工具記錄的相關事件的詳情可查看官方文檔:https://redis.io/topics/latency-monitor
如獲取最近的?latency
127.0.0.1:6379>?debug?sleep?2 OK (2.00s) 127.0.0.1:6379>?latency?latest 1)?1)?"command" ???2)?(integer)?1645330616 ???3)?(integer)?2003 ???4)?(integer)?2003
事件的名稱;
事件發生的最新延遲的?Unix?時間戳;
毫秒為單位的時間延遲;
該事件的最大延遲。
如何解決?Redis?變慢?
Redis?的數據讀寫由單線程執行,如果主線程執行的操作時間太長,就會導致主線程阻塞。
一起分析下都有哪些操作會阻塞主線程,我們又該如何解決?
網絡通信導致的延遲
客戶端使用?TCP/IP?連接或?Unix?域連接連接到?Redis。1?Gbit/s?網絡的典型延遲約為?200?us。
redis?客戶端執行一條命令分?4?個過程:
發送命令-〉?命令排隊?-〉?命令執行-〉?返回結果
這個過程稱為?Round?trip?time(簡稱?RTT,?往返時間),mget?mset?有效節約了?RTT,但大部分命令(如?hgetall,并沒有?mhgetall)不支持批量操作,需要消耗?N?次?RTT?,這個時候需要?pipeline?來解決這個問題。
Redis?pipeline?將多個命令連接在一起來減少網絡響應往返次數。
redis-pipeline
慢指令導致的延遲
根據上文的慢指令監控查詢文檔,查詢到慢查詢指令。可以通過以下兩種方式解決:
比如在?Cluster?集群中,將聚合運算等?O(N)?操作運行在?slave?上,或者在客戶端完成。
使用高效的命令代替。使用增量迭代的方式,避免一次查詢大量數據,具體請查看SCAN、SSCAN、HSCAN和ZSCAN命令。
除此之外,生產中禁用KEYS?命令,它只適用于調試。因為它會遍歷所有的鍵值對,所以操作延時高。
Fork?生成?RDB?導致的延遲
生成?RDB?快照,Redis?必須?fork?后臺進程。fork?操作(在主線程中運行)本身會導致延遲。
Redis?使用操作系統的多進程寫時復制技術?COW(Copy?On?Write)?來實現快照持久化,減少內存占用。
寫時復制技術保證快照期間數據可修改
但?fork?會涉及到復制大量鏈接對象,一個?24?GB?的大型?Redis?實例需要?24?GB?/?4?kB?*?8?=?48?MB?的頁表。
執行?bgsave?時,這將涉及分配和復制?48?MB?內存。
此外,從庫加載?RDB?期間無法提供讀寫服務,所以主庫的數據量大小控制在?2~4G?左右,讓從庫快速的加載完成。
內存大頁(transparent?huge?pages)
常規的內存頁是按照?4?KB?來分配,Linux?內核從?2.6.38?開始支持內存大頁機制,該機制支持?2MB?大小的內存頁分配。
Redis?使用了?fork?生成?RDB?做持久化提供了數據可靠性保證。
當生成?RDB?快照的過程中,Redis?采用**寫時復制**技術使得主線程依然可以接收客戶端的寫請求。
也就是當數據被修改的時候,Redis?會復制一份這個數據,再進行修改。
采用了內存大頁,生成?RDB?期間,即使客戶端修改的數據只有?50B?的數據,Redis?需要復制?2MB?的大頁。當寫的指令比較多的時候就會導致大量的拷貝,導致性能變慢。
使用以下指令禁用?Linux?內存大頁即可:
echo?never?>?/sys/kernel/mm/transparent_hugepage/enabled
swap:操作系統分頁
當物理內存(內存條)不夠用的時候,將部分內存上的數據交換到?swap?空間上,以便讓系統不會因內存不夠用而導致?oom?或者更致命的情況出現。
當某進程向?OS?請求內存發現不足時,OS?會把內存中暫時不用的數據交換出去,放在?SWAP?分區中,這個過程稱為?SWAP?OUT。
當某進程又需要這些數據且?OS?發現還有空閑物理內存時,又會把?SWAP?分區中的數據交換回物理內存中,這個過程稱為?SWAP?IN。
內存?swap?是操作系統里將內存數據在內存和磁盤間來回換入和換出的機制,涉及到磁盤的讀寫。
觸發?swap?的情況有哪些呢?
對于?Redis?而言,有兩種常見的情況:
Redis?使用了比可用內存更多的內存;
與?Redis?在同一機器運行的其他進程在執行大量的文件讀寫?I/O?操作(包括生成大文件的?RDB?文件和?AOF?后臺線程),文件讀寫占用內存,導致?Redis?獲得的內存減少,觸發了?swap。
我要如何排查是否因為?swap?導致的性能變慢呢?
Linux?提供了很好的工具來排查這個問題,所以當懷疑由于交換導致的延遲時,只需按照以下步驟排查。
獲取?Redis?實例?pid
$?redis-cli?info?|?grep?process_id process_id:13160
進入此進程的?/proc?文件系統目錄:
cd?/proc/13160
在這里有一個?smaps?的文件,該文件描述了?Redis?進程的內存布局,運行以下指令,用?grep?查找所有文件中的?Swap?字段。
$?cat?smaps?|?egrep?'^(Swap|Size)' Size:????????????????316?kB Swap:??????????????????0?kB Size:??????????????????4?kB Swap:??????????????????0?kB Size:??????????????????8?kB Swap:??????????????????0?kB Size:?????????????????40?kB Swap:??????????????????0?kB Size:????????????????132?kB Swap:??????????????????0?kB Size:?????????????720896?kB Swap:?????????????????12?kB
每行?Size?表示?Redis?實例所用的一塊內存大小,和?Size?下方的?Swap?對應這塊?Size?大小的內存區域有多少數據已經被換出到磁盤上了。
如果?Size?==?Swap?則說明數據被完全換出了。
可以看到有一個?720896?kB?的內存大小有?12?kb?被換出到了磁盤上(僅交換了?12?kB),這就沒什么問題。
Redis?本身會使用很多大小不一的內存塊,所以,你可以看到有很多?Size?行,有的很小,就是?4KB,而有的很大,例如?720896KB。不同內存塊被換出到磁盤上的大小也不一樣。
敲重點了
如果?Swap?一切都是?0?kb,或者零星的?4k?,那么一切正常。
當出現百?MB,甚至?GB?級別的?swap?大小時,就表明,此時,Redis?實例的內存壓力很大,很有可能會變慢。
解決方案
增加機器內存;
將?Redis?放在單獨的機器上運行,避免在同一機器上運行需要大量內存的進程,從而滿足?Redis?的內存需求;
增加?Cluster?集群的數量分擔數據量,減少每個實例所需的內存。
AOF?和磁盤?I/O?導致的延遲
為了保證數據可靠性,Redis?使用?AOF?和?RDB?快照實現快速恢復和持久化。
可以使用?appendfsync?配置將?AOF?配置為以三種不同的方式在磁盤上執行?write?或者?fsync?(可以在運行時使用?CONFIG?SET命令修改此設置,比如:redis-cli?CONFIG?SET?appendfsync?no)。
-
no:Redis?不執行?fsync,唯一的延遲來自于?write?調用,write?只需要把日志記錄寫到內核緩沖區就可以返回。
-
everysec:Redis?每秒執行一次?fsync。使用后臺子線程異步完成?fsync?操作。最多丟失?1s?的數據。
-
always:每次寫入操作都會執行?fsync,然后用?OK?代碼回復客戶端(實際上?Redis?會嘗試將同時執行的許多命令聚集到單個?fsync?中),沒有數據丟失。在這種模式下,性能通常非常低,強烈建議使用快速磁盤和可以在短時間內執行?fsync?的文件系統實現。
我們通常將?Redis?用于緩存,數據丟失完全惡意從數據獲取,并不需要很高的數據可靠性,建議設置成?no?或者?everysec。
除此之外,避免?AOF?文件過大,?Redis?會進行?AOF?重寫,生成縮小的?AOF?文件。
可以把配置項?no-appendfsync-on-rewrite設置為?yes,表示在?AOF?重寫時,不進行?fsync?操作。
也就是說,Redis?實例把寫命令寫到內存后,不調用后臺線程進行?fsync?操作,就直接返回了。
expires?淘汰過期數據
Redis?有兩種方式淘汰過期數據:
-
惰性刪除:當接收請求的時候發現?key?已經過期,才執行刪除;
-
定時刪除:每?100?毫秒刪除一些過期的?key。
定時刪除的算法如下:
隨機采樣?ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP個數的?key,刪除所有過期的?key;
如果發現還有超過?25%?的?key?已過期,則執行步驟一。
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默認設置為?20,每秒執行?10?次,刪除?200?個?key?問題不大。
如果觸發了第二條,就會導致?Redis?一致在刪除過期數據去釋放內存。而刪除是阻塞的。
觸發條件是什么呀?
也就是大量的?key?設置了相同的時間參數。同一秒內,大量?key?過期,需要重復刪除多次才能降低到?25%?以下。
簡而言之:大量同時到期的?key?可能會導致性能波動。
解決方案
如果一批?key?的確是同時過期,可以在?EXPIREAT?和?EXPIRE?的過期時間參數上,加上一個一定大小范圍內的隨機數,這樣,既保證了?key?在一個鄰近時間范圍內被刪除,又避免了同時過期造成的壓力。
bigkey
通常我們會將含有較大數據或含有大量成員、列表數的?Key?稱之為大?Key,下面我們將用幾個實際的例子對大?Key?的特征進行描述:
-
一個?STRING?類型的?Key,它的值為?5MB(數據過大)
-
一個?LIST?類型的?Key,它的列表數量為?10000?個(列表數量過多)
-
一個?ZSET?類型的?Key,它的成員數量為?10000?個(成員數量過多)
-
一個?HASH?格式的?Key,它的成員數量雖然只有?1000?個但這些成員的?value?總大小為?10MB(成員體積過大)
bigkey?帶來問題如下:
-
Redis?內存不斷變大引發?OOM,或者達到?maxmemory?設?置值引發寫阻塞或重要?Key?被逐出;
-
Redis?Cluster?中的某個?node?內存遠超其余?node,但因?Redis?Cluster?的數據遷移最小粒度為?Key?而無法將?node?上的內存均衡化;
-
bigkey?的讀請求占用過大帶寬,自身變慢的同時影響到該服務器上的其它服務;
-
刪除一個?bigkey?造成主庫較長時間的阻塞并引發同步中斷或主從切換;
查找?bigkey
使用?redis-rdb-tools?工具以定制化方式找出大?Key。
解決方案
對大?key?拆分
如將一個含有數萬成員的?HASH?Key?拆分為多個?HASH?Key,并確保每個?Key?的成員數量在合理范圍,在?Redis?Cluster?結構中,大?Key?的拆分對?node?間的內存平衡能夠起到顯著作用。
異步清理大?key
Redis?自?4.0?起提供了?UNLINK?命令,該命令能夠以非阻塞的方式緩慢逐步的清理傳入的?Key,通過?UNLINK,你可以安全的刪除大?Key?甚至特大?Key。
總結
如下檢查清單,幫助你在遇到?Redis?性能變慢的時候能高效解決問題。
獲取當前?Redis?的基線性能;
開啟慢指令監控,定位慢指令導致的問題;
找到慢指令,使用?scan?的方式;
將實例的數據大小控制在?2-4GB,避免主從復制加載過大?RDB?文件而阻塞;
禁用內存大頁,采用了內存大頁,生成?RDB?期間,即使客戶端修改的數據只有?50B?的數據,Redis?需要復制?2MB?的大頁。當寫的指令比較多的時候就會導致大量的拷貝,導致性能變慢。
Redis?使用的內存是否過大導致?swap;
AOF?配置是否合理,可以將配置項?no-appendfsync-on-rewrite?設置為?yes,避免?AOF?重寫和?fsync?競爭磁盤?IO?資源,導致?Redis?延遲增加。
bigkey?會帶來一系列問題,我們需要進行拆分防止出現?bigkey,并通過?UNLINK?異步刪除。
推薦學習:redis