掃二維碼與項目經理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網交流
?誰曾想,凌晨 12 點之后,用戶量暴增,出現(xiàn)了一個技術故障,用戶無法下單,當時老大火冒三丈!

為企業(yè)提供網站建設、成都做網站、網站優(yōu)化、營銷型網站、競價托管、品牌運營等營銷獲客服務。創(chuàng)新互聯(lián)建站擁有網絡營銷運營團隊,以豐富的互聯(lián)網營銷經驗助力企業(yè)精準獲客,真正落地解決中小企業(yè)營銷獲客難題,做到“讓獲客更簡單”。自創(chuàng)立至今,成功用技術實力解決了企業(yè)“網站建設、網絡品牌塑造、網絡營銷”三大難題,同時降低了營銷成本,提高了有效客戶轉化率,獲得了眾多企業(yè)客戶的高度認可!
經過查找發(fā)現(xiàn) Redis 報 Could not get a resource from the pool。
獲取不到連接資源,并且集群中的單臺 Redis 連接量很高。
大量的流量沒了 Redis 的緩存響應,直接打到了 MySQL,最后數據庫也宕機了……
于是各種更改最大連接數、連接等待數,雖然報錯信息頻率有所緩解,但還是持續(xù)報錯。
后來經過線下測試,發(fā)現(xiàn)存放 Redis 中的字符數據很大,平均 1s 返回數據。
可以發(fā)現(xiàn),一旦 Redis 延遲過高,會引發(fā)各種問題。
今天跟大家一起來分析下如何確定 Redis 有性能問題和解決方案。
1、延遲基線測量
2、慢指令監(jiān)控
3、網絡通信導致的延遲
4、慢指令導致的延遲
5、Fork 生成 RDB 導致的延遲
6、內存大頁(transparent huge pages)
7、swap:操作系統(tǒng)分頁
8、AOF 和磁盤 I/O 導致的延遲
9、expires 淘汰過期數據
10、bigkey
最大延遲是客戶端發(fā)出命令到客戶端收到命令的響應的時間,正常情況下 Redis 處理的時間極短,在微秒級別。
當 Redis 出現(xiàn)性能波動的時候,比如達到幾秒到十幾秒,這個很明顯我們可以認定 Redis 性能變慢了。
有的硬件配置比較高,當延遲 0.6ms,我們可能就認定變慢了。硬件比較差的可能 3 ms 我們才認為出現(xiàn)問題。
那我們該如何定義 Redis 真的變慢了呢?
所以,我們需要對當前環(huán)境的 Redis 基線性能做測量,也就是在一個系統(tǒng)在低壓力、無干擾情況下的基本性能。
當你發(fā)現(xiàn) Redis 運行時時的延遲是基線性能的 2 倍以上,就可以判定 Redis 性能變慢了。
redis-cli 命令提供了–intrinsic-latency 選項,用來監(jiān)測和統(tǒng)計測試期間內的最大延遲(以毫秒為單位),這個延遲可以作為 Redis 的基線性能。
redis-cli --latency -h `host` -p `port`
比如執(zhí)行如下指令:
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是測試將執(zhí)行的秒數。我們運行測試的時間越長,我們就越有可能發(fā)現(xiàn)延遲峰值。
通常運行 100 秒通常是合適的,足以發(fā)現(xiàn)延遲問題了,當然我們可以選擇不同時間運行幾次,避免誤差。
運行的最大延遲是 3079 微秒,所以基線性能是 3079 (3 毫秒)微秒。
需要注意的是,我們要在 Redis 的服務端運行,而不是客戶端。這樣,可以避免網絡對基線性能的影響。
可以通過 -h host -p port 來連接服務端,如果想監(jiān)測網絡對 Redis 的性能影響,可以使用 Iperf 測量客戶端到服務端的網絡延遲。
如果網絡延遲幾百毫秒,說明網絡可能有其他大流量的程序在運行導致網絡擁塞,需要找運維協(xié)調網絡的流量分配。
如何判斷是否是慢指令呢?
看操作復雜度是否是O(N)。官方文檔對每個命令的復雜度都有介紹,盡可能使用O(1) 和 O(log N)命令。
涉及到集合操作的復雜度一般為O(N),比如集合全量查詢HGETALL、SMEMBERS,以及集合的聚合操作:SORT、LREM、 SUNION等。
有監(jiān)控數據可以觀測呢?代碼不是我寫的,不知道有沒有人用了慢指令。
有兩種方式可以排查到:
此外,可以使用自己(top、htop、prstat 等)快速檢查 Redis 主進程的 CPU 消耗。如果 CPU 使用率很高而流量不高,通常表明使用了慢速命令。
1)慢日志功能
Redis 中的 slowlog 命令可以讓我們快速定位到那些超出指定執(zhí)行時間的慢命令,默認情況下命令若是執(zhí)行時間超過 10ms 就會被記錄到日志。
slowlog 只會記錄其命令執(zhí)行的時間,不包含 io 往返操作,也不記錄單由網絡延遲引起的響應慢。
我們可以根據基線性能來自定義慢命令的標準(配置成基線性能最大延遲的 2 倍),調整觸發(fā)記錄慢命令的閾值。
可以在 redis-cli 中輸入以下命令配置記錄 6 毫秒以上的指令:
redis-cli CONFIG SET slowlog-log-slower-than 6000
也可以在 Redis.config 配置文件中設置,以微秒為單位。
想要查看所有執(zhí)行時間比較慢的命令,可以通過使用 Redis-cli 工具,輸入 slowlog get 命令查看,返回結果的第三個字段以微秒位單位顯示命令的執(zhí)行時間。
假如只需要查看最后 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 個字段:
2)Latency Monitoring
Redis 在 2.8.13 版本引入了 Latency Monitoring 功能,用于以秒為粒度監(jiān)控各種事件的發(fā)生頻率。
啟用延遲監(jiān)視器的第一步是設置延遲閾值(單位毫秒)。只有超過該閾值的時間才會被記錄,比如我們根據基線性能(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
如何解決 Redis 變慢?
Redis 的數據讀寫由單線程執(zhí)行,如果主線程執(zhí)行的操作時間太長,就會導致主線程阻塞。
一起分析下都有哪些操作會阻塞主線程,我們又該如何解決?
客戶端使用 TCP/IP 連接或 Unix 域連接連接到 Redis。1 Gbit/s 網絡的典型延遲約為 200 us。
redis 客戶端執(zhí)行一條命令分 4 個過程:
發(fā)送命令-〉 命令排隊 -〉 命令執(zhí)行-〉 返回結果
這個過程稱為 Round trip time(簡稱 RTT, 往返時間),mget mset 有效節(jié)約了 RTT,但大部分命令(如 hgetall,并沒有 mhgetall)不支持批量操作,需要消耗 N 次 RTT ,這個時候需要 pipeline 來解決這個問題。
Redis pipeline 將多個命令連接在一起來減少網絡響應往返次數。
redis-pipeline
根據上文的慢指令監(jiān)控查詢文檔,查詢到慢查詢指令??梢酝ㄟ^以下兩種方式解決:
除此之外,生產中禁用KEYS 命令,它只適用于調試。因為它會遍歷所有的鍵值對,所以操作延時高。
生成 RDB 快照,Redis 必須 fork 后臺進程。fork 操作(在主線程中運行)本身會導致延遲。
Redis 使用操作系統(tǒng)的多進程寫時復制技術 COW(Copy On Write) 來實現(xiàn)快照持久化,減少內存占用。
?
寫時復制技術保證快照期間數據可修改
但 fork 會涉及到復制大量鏈接對象,一個 24 GB 的大型 Redis 實例需要 24 GB / 4 kB * 8 = 48 MB 的頁表。
執(zhí)行 bgsave 時,這將涉及分配和復制 48 MB 內存。
此外,從庫加載 RDB 期間無法提供讀寫服務,所以主庫的數據量大小控制在 2~4G 左右,讓從庫快速的加載完成。?
常規(guī)的內存頁是按照 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 空間上,以便讓系統(tǒng)不會因內存不夠用而導致 oom 或者更致命的情況出現(xiàn)。
當某進程向 OS 請求內存發(fā)現(xiàn)不足時,OS 會把內存中暫時不用的數據交換出去,放在 SWAP 分區(qū)中,這個過程稱為 SWAP OUT。
當某進程又需要這些數據且 OS 發(fā)現(xiàn)還有空閑物理內存時,又會把 SWAP 分區(qū)中的數據交換回物理內存中,這個過程稱為 SWAP IN。
內存 swap 是操作系統(tǒng)里將內存數據在內存和磁盤間來回換入和換出的機制,涉及到磁盤的讀寫。?
觸發(fā) swap 的情況有哪些呢?
對于 Redis 而言,有兩種常見的情況:
我要如何排查是否因為 swap 導致的性能變慢呢?
Linux 提供了很好的工具來排查這個問題,所以當懷疑由于交換導致的延遲時,只需按照以下步驟排查。
1)獲取 Redis 實例 pid
$ redis-cli info | grep process_id
process_id:13160
進入此進程的 /proc 文件系統(tǒng)目錄:
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 大小的內存區(qū)域有多少數據已經被換出到磁盤上了。
如果 Size == Swap 則說明數據被完全換出了。
可以看到有一個 720896 kB 的內存大小有 12 kb 被換出到了磁盤上(僅交換了 12 kB),這就沒什么問題。
Redis 本身會使用很多大小不一的內存塊,所以,你可以看到有很多 Size 行,有的很小,就是 4KB,而有的很大,例如 720896KB。不同內存塊被換出到磁盤上的大小也不一樣。
敲重點了!
如果 Swap 一切都是 0 kb,或者零星的 4k ,那么一切正常。
當出現(xiàn)百 MB,甚至 GB 級別的 swap 大小時,就表明,此時,Redis 實例的內存壓力很大,很有可能會變慢。
2)解決方案
為了保證數據可靠性,Redis 使用 AOF 和 RDB 快照實現(xiàn)快速恢復和持久化。
可以使用 appendfsync 配置將 AOF 配置為以三種不同的方式在磁盤上執(zhí)行 write 或者 fsync (可以在運行時使用 CONFIG SET命令修改此設置,比如:redis-cli CONFIG SET appendfsync no)。
我們通常將 Redis 用于緩存,數據丟失完全惡意從數據獲取,并不需要很高的數據可靠性,建議設置成 no 或者 everysec。
除此之外,避免 AOF 文件過大, Redis 會進行 AOF 重寫,生成縮小的 AOF 文件。
可以把配置項 no-appendfsync-on-rewrite設置為 yes,表示在 AOF 重寫時,不進行 fsync 操作。
也就是說,Redis 實例把寫命令寫到內存后,不調用后臺線程進行 fsync 操作,就直接返回了。
Redis 有兩種方式淘汰過期數據:
定時刪除的算法如下:
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默認設置為 20,每秒執(zhí)行 10 次,刪除 200 個 key 問題不大。
如果觸發(fā)了第二條,就會導致 Redis 一致在刪除過期數據去釋放內存。而刪除是阻塞的。
碼哥,觸發(fā)條件是什么呀?
也就是大量的 key 設置了相同的時間參數。同一秒內,大量 key 過期,需要重復刪除多次才能降低到 25% 以下。
簡而言之:大量同時到期的 key 可能會導致性能波動。
1)解決方案
如果一批 key 的確是同時過期,可以在 EXPIREAT 和 EXPIRE 的過期時間參數上,加上一個一定大小范圍內的隨機數,這樣,既保證了 key 在一個鄰近時間范圍內被刪除,又避免了同時過期造成的壓力。
通常我們會將含有較大數據或含有大量成員、列表數的 Key 稱之為大 Key,下面我們將用幾個實際的例子對大 Key 的特征進行描述:
bigkey 帶來問題如下:
1)查找 bigkey
使用 redis-rdb-tools 工具以定制化方式找出大 Key。
2)解決方案
如將一個含有數萬成員的 HASH Key 拆分為多個 HASH Key,并確保每個 Key 的成員數量在合理范圍,在 Redis Cluster 結構中,大 Key 的拆分對 node 間的內存平衡能夠起到顯著作用。
Redis 自 4.0 起提供了 UNLINK 命令,該命令能夠以非阻塞的方式緩慢逐步的清理傳入的 Key,通過 UNLINK,你可以安全的刪除大 Key 甚至特大 Key。
如下檢查清單,幫助你在遇到 Redis 性能變慢的時候能高效解決問題:

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網交流