Redis是不是真的變慢了?

大家好,今天我們來學習一下如何確定Redis是不是真的變慢了。

我們在使用redis時一定會遇到變慢的時候,那我們如何來判斷Redis是否真的變慢了呢, 一個最直接的方法就是查看Redis的響應延遲,一般情況下,Redis延遲很低,但是在某些時刻, Redis實例會出現比較高的響應延遲,甚至能達到幾秒到十幾秒,當你發現Redis命令的執行時間突然就增長到了幾秒,基本就可以認定 Redis 變慢了。這種方法是看 Redis 延遲的絕對值。當我們不能根據延遲的絕對值來判斷redis是否真的變慢了,我們還有一種方法可以判斷,那就是redis的基準線性能。

redis從2.8.7版本開始,redis-cli命令提供了–-intrinsic-latency 選項,可以用來監測和統計測試期間內的最大延遲,這個延遲可以作為 Redis 的基準線性能。其中,測試時長可以用–-intrinsic-latency 選項的參數來指定。比如我們執行redis-cli –-intrinsic-latency 30這個命令,該命令會列印出30秒內檢測到的最大延遲。如下所示,這裡的最大延遲是3595微妙,所以我們把該redis實例的基準線性能是3595微妙。

./redis-cli –-intrinsic-latency 30

Max latency so far: 1 microseconds.
Max latency so far: 4 microseconds.
Max latency so far: 14 microseconds.
Max latency so far: 33 microseconds.
Max latency so far: 48 microseconds.
Max latency so far: 51 microseconds.
Max latency so far: 100 microseconds.
Max latency so far: 110 microseconds.
Max latency so far: 488 microseconds.
Max latency so far: 944 microseconds.
Max latency so far: 1590 microseconds.
Max latency so far: 1921 microseconds.
Max latency so far: 3595 microseconds.

584098163 total runs (avg latency: 0.0514 microseconds / 51.36 nanoseconds per run).
Worst run took 69994x longer than the average latency.

    一般來說,當你觀察到Redis運行時延遲是其基準線性能的2倍及以上,就可以認定 Redis 變慢了。在確定Redis變慢之後,我們需要去進一步去排查Redis變慢的原因。

    我們不能沒有章法的去排查redis是如何變慢的,我們需要基於自己對Redis本身的工作原理的理解, 並且結合和它交互的作業系統、存儲以及網路等外部系統關鍵機制,再藉助一些輔助工具來定位原因,並制定有效的解決方案。 影響Redis性能的三大要素是Redis自身的操作特性、文件系統和作業系統。下面我們來分別看一下。

Redis自身操作特性的影響:

下面我們重點介紹兩個常見的操作,這兩個操作是經常導致redis變慢的罪魁禍首。

1.慢查詢命令

當redis變慢時,我們可以通過日誌或者latency monitor工具來查看Redis中查詢變慢的請求,然後根據請求對應的具體命令以及官方文檔,確認下是否採用了複雜度高的慢查詢命令。如果的確有大量的慢查詢命令,有兩種處理方式:

  1. 用其它高效命令代替。
  2. 當你需要執行排序、交集、並集操作時,可以在客戶端完成,而不要用 SORT、SUNION、SINTER 這些命令,以免拖慢 Redis 實例。

2.過期key操作

我們可以對edis的鍵值對設置過期時間。默認情況下,Redis每100毫秒會刪除一些過期key,具體的演算法如下:

  1. 取樣ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 個數的 key,並將其中過期的 key 全部刪除;

  2. 如果超過 25% 的 key 過期了,則重複刪除的過程,直到過期 key 的比例降至 25% 以下。

    如果我們同一時間有大量過期的key要刪除,就會觸發第二條策略,Redis 就會一直刪除以釋放記憶體空間。注意,刪除操作是阻塞的(Redis 4.0 後可以用非同步執行緒機制來減少阻塞影響)。所以,一旦該條件觸發,Redis 的執行緒就會一直執行刪除,這樣一來,就沒辦法正常服務其他的鍵值操作了,就會進一步引起其他鍵值操作的延遲增加,Redis 就會變慢。所以我們需要避免給一批key設置相同的過期時間,

如果業務上確實需要一批key同時過期,我們可以在EXPIREAT 和 EXPIRE 的過期時間參數上 加上一個一定大小範圍內的隨機數,這樣,既可以保證key 在一個鄰近時間範圍內被刪除,又避免了同時過期造成的壓力。

文件系統:AOF日誌

Redis會採用AOF日誌或者RDB來保證數據的可靠性。其中AOF日誌提供了三種日誌寫回策略: no、everysec、always。這三種寫回策略依賴文件系統的兩個系統調用,也就是write和fsync。write 只要把日誌記錄寫到內核緩衝區,就可以返回了,並不需要等待日誌實際寫回到磁碟;而fsync 需要把日誌記錄寫回到磁碟後才能返回,時間較長。下面這張表展示了三種寫回策略所執行的系統調用:

image.png

當AOF配置成everysec時,Redis允許丟失一秒的操作記錄,所以Redis主執行緒不需要確保每個操作記錄日誌都寫回到磁碟。所以,當配置為everysec時,Redis是使用後台的子執行緒非同步完成fsync的操作。當AOF配置成always時,Redis需要確保每個操作記錄日誌都寫回磁碟,如果採用後檯子執行緒非同步完成,主執行緒就無法及時地知道每個操作是否已經完成了。這就不符合 always 策略的要求了。所以,always 策略並不使用後檯子執行緒來執行。

我們在這裡知道,Redis持久化機制在使用 AOF 日誌時,為了避免日AOF志文件變得太大,Redis 會採用後檯子進程來進行AOF重寫。但是,這裡會出現一個風險點,AOF重寫會對磁碟進行大量的IO操作,同時fsync需要等到數據寫入到磁碟後才會返回。所以,當AOF重新操作磁碟壓力比較大時,就會導致fsync被阻塞。儘管fsync是由後檯子執行緒負責執行的,但是,主執行緒會監控fsync的執行進度。當主執行緒使用後檯子執行緒執行了一次 fsync,需要再次把新接收的操作記錄寫回磁碟時,如果主執行緒發現上一次的 fsync 還沒有執行完,那麼它就會阻塞。所以,如果後檯子執行緒執行的 fsync 頻繁阻塞的話主執行緒也會阻塞,導致 Redis 性能變慢。

到目前為止,你已經知道了,當AOF重寫導致磁碟壓力大時,就會導致fsync阻塞,進而阻塞主執行緒 ,導致延遲增加。下面我們來看如何排查和解決這個問題。首先,我們可以先檢查配置文件中的 appendfsync配置項,查看AOF的寫入策略是什麼樣的。

image.png

如果我們的業務方對Redis的延遲很敏感,但是可以允許有一定數據的數據丟失,我們可以設置no-appendfsync-on-rewrite為yes

no-appendfsync-on-rewrite yes

這個配置項設置為 yes 時,表示在 AOF 重寫時,不進行 fsync 操作。也就是說,Redis 實例把寫命令寫到記憶體後,不調用後台執行緒進行 fsync 操作,就可以直接返回了。當然,如果此時實例發生宕機,就會導致數據丟失。反之,如果這個配置項設置為 no(默認配置),在 AOF 重寫時,Redis 實例仍然會調用後台執行緒進行 fsync 操作,這就會給實例帶來阻塞。如果業務方既需要低延遲也需要高可靠性,我們可以採用固態硬碟作為AOF日誌的寫入設備。

作業系統:

1.記憶體swap。

記憶體 swap 是作業系統里將記憶體數據在記憶體和磁碟間來回換入和換出的機制,會涉及到磁碟的讀寫,所以,一旦觸發 swap,其性能都會受到慢速磁碟讀寫的影響。

Redis是記憶體資料庫,如果沒有控制好記憶體的使用量,就可能會引起作業系統的swap。一旦swap被觸發了,Redis的讀寫操作就會有可能請求到磁碟,從而影響Redis的主執行緒執行,所以會增大Redis的響應時間。

作業系統觸發swap機制,主要是因為物理機的記憶體不足導致的,所以,當發現作業系統進行了記憶體swap,最直接的處理方式就是增加物理機的記憶體大小。

2.記憶體大頁機制。

我們先來看看什麼是記憶體大頁?我們都知道,應用程式向作業系統申請記憶體時,是按記憶體頁進行申請的,而常規的記憶體頁大小是 4KB。Linux 內核從 2.6.38 開始,支援了記憶體大頁機制,該機制允許應用程式以 2MB 大小為單位,向作業系統申請記憶體。 我們在Redis持久化機制里知道,Redis的持久化是採用寫時複製的,也就是說,一旦有數據要被修改,Redis 並不會直接修改記憶體中的數據,而是將這些數據拷貝一份,然後再進行修改。

如果採用了記憶體大頁,那麼,即使客戶端請求只修改 1kb 的數據,Redis 也需要拷貝 2MB 的大頁。相反,如果是常規記憶體頁機制,只用拷貝 4KB。兩者相比,你可以看到,當客戶端請求修改或新寫入數據較多時,記憶體大頁機制將導致大量的拷貝,這就會影響 Redis 正常的訪存操作,最終導致性能變慢。解決這個問題的方式,就是關閉大頁機制。

我們在linux執行

cat /sys/kernel/mm/transparent_hugepage/enabled

如果是always,就表明啟動了記憶體大頁機制,我們執行

echo never /sys/kernel/mm/transparent_hugepage/enabled

來關閉記憶體大頁機制就好了。

 

今天的分享就到這裡。更多硬核知識,請關注公眾號「程式設計師學長」。