Redis突然變慢?一起分析如何確定Redis有效能問題以及如何解決
這篇文章為大家帶來了關於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 。
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(成員體積過大)
- Redis 記憶體不斷變大引發 OOM,或達到 maxmemory 設 置值引發寫入阻塞或重要 Key 逐出;
Redis Cluster 中的某個 node 記憶體遠超過其餘 node,但因 Redis Cluster 的資料遷移最小粒徑為 Key 而無法將 node 上的記憶體均衡化;
#11
##bigkey 的讀取請求佔用過大頻寬,自身變慢的同時影響到該伺服器上的其它服務;
刪除一個 bigkey 造成主函式庫較長的時間阻塞並引發同步中斷或主從切換;
尋找 bigkey
使用 redis-rdb-tools 工具以客製化方式找出大 Key。
解
對大 key 分割
如將一個含有數萬成員的 HASH Key 分割為多個 HASH Key,並確保每個 Key 的成員數量在合理範圍,在 Redis Cluster 結構中,大 Key 的分割對 node 間的記憶體平衡能夠發揮顯著作用。
非同步清理大 keyRedis 自 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學習教學######以上是Redis突然變慢?一起分析如何確定Redis有效能問題以及如何解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

如何清空 Redis 數據:使用 FLUSHALL 命令清除所有鍵值。使用 FLUSHDB 命令清除當前選定數據庫的鍵值。使用 SELECT 切換數據庫,再使用 FLUSHDB 清除多個數據庫。使用 DEL 命令刪除特定鍵。使用 redis-cli 工具清空數據。

使用 Redis 指令需要以下步驟:打開 Redis 客戶端。輸入指令(動詞 鍵 值)。提供所需參數(因指令而異)。按 Enter 執行指令。 Redis 返迴響應,指示操作結果(通常為 OK 或 -ERR)。

使用Redis進行鎖操作需要通過SETNX命令獲取鎖,然後使用EXPIRE命令設置過期時間。具體步驟為:(1) 使用SETNX命令嘗試設置一個鍵值對;(2) 使用EXPIRE命令為鎖設置過期時間;(3) 當不再需要鎖時,使用DEL命令刪除該鎖。

要從 Redis 讀取隊列,需要獲取隊列名稱、使用 LPOP 命令讀取元素,並處理空隊列。具體步驟如下:獲取隊列名稱:以 "queue:" 前綴命名,如 "queue:my-queue"。使用 LPOP 命令:從隊列頭部彈出元素並返回其值,如 LPOP queue:my-queue。處理空隊列:如果隊列為空,LPOP 返回 nil,可先檢查隊列是否存在再讀取元素。

Redis 使用哈希表存儲數據,支持字符串、列表、哈希表、集合和有序集合等數據結構。 Redis 通過快照 (RDB) 和追加只寫 (AOF) 機制持久化數據。 Redis 使用主從復制來提高數據可用性。 Redis 使用單線程事件循環處理連接和命令,保證數據原子性和一致性。 Redis 為鍵設置過期時間,並使用 lazy 刪除機制刪除過期鍵。

理解 Redis 源碼的最佳方法是逐步進行:熟悉 Redis 基礎知識。選擇一個特定的模塊或功能作為起點。從模塊或功能的入口點開始,逐行查看代碼。通過函數調用鏈查看代碼。熟悉 Redis 使用的底層數據結構。識別 Redis 使用的算法。

Redis 作為消息中間件,支持生產-消費模型,可持久化消息並保證可靠交付。使用 Redis 作為消息中間件可實現低延遲、可靠和可擴展的消息傳遞。
