1.是否使用復雜度過高的命令
首先,第一步,你需要去查看一下 Redis 的慢日志(slowlog)。
Redis 提供了慢日志命令的統計功能,它記錄了有哪些命令在執行時耗時比較久。
查看 Redis 慢日志之前,你需要設置慢日志的閾值。例如,設置慢日志的閾值為 5 毫秒,并且保留最近 500 條慢日志記錄:
redis-cli -h 127.0.0.1 -p 6379
# 命令執行耗時超過 5 微秒,記錄慢日志
CONFIG SET slowlog-log-slower-than 5
# 只保留最近 500 條慢日志
CONFIG SET slowlog-max-len 500
#獲取最近的 10 條慢查詢命令
redis-cli SLOWLOG GET 10
(1)查看日志中是否使用 O(N) 以上復雜度的命令,例如 SORT、SUNION、ZUNIONSTORE 聚合類命令
(2)Redis 一次需要返回給客戶端的數據過多,更多時間花費在數據協議的組裝和網絡傳輸過程中。
優化:
(1)對于數據的聚合操作,放在客戶端做
(2)每次獲取盡量少的數據,讓 Redis 可以及時處理返回
2. 是否操作 bigkey
如果你查詢慢日志發現,并不是復雜度過高的命令導致的,而都是 SET / DEL 這種簡單命令出現在慢日志中,那么需要判斷實例是否寫入了 bigkey
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 1
-------- summary -------
Sampled 829675 keys in the keyspace!
Total key length in bytes is 10059825 (avg len 12.13)
Biggest string found 'key:291880' has 10 bytes
Biggest list found 'mylist:004' has 40 items
Biggest set found 'myset:2386' has 38 members
Biggest hash found 'myhash:3574' has 37 fields
Biggest zset found 'myzset:2704' has 42 members
36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)
787393 lists with 896540 items (94.90% of keys, avg size 1.14)
1994 sets with 40052 members (00.24% of keys, avg size 20.09)
1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)
1985 zsets with 39750 members (00.24% of keys, avg size 20.03)
3. 數據是否集中過期
當redis中大量數據集中過期時,請求穿透到mysql等關系型數據庫,會導致查詢速度變慢,從而影響redis響應變慢
在某個時間點突然出現一波延時,其現象表現為:變慢的時間點很有規律,例如某個整點,或者每間隔多久就會發生一波延遲。
優化:
(1)集中過期 key 增加一個隨機過期時間,把集中過期的時間打散,降低 Redis 清理過期 key 的壓力
# 在過期時間點之后的 5 分鐘內隨機過期掉
redis.expireat(key, expire_time + random(300))
(2)如果使用的 Redis 是 4.0 以上版本,可以開啟 lazy-free 機制,當刪除過期 key 時,把釋放內存的操作放到后臺線程中執行,避免阻塞主線程。
# 釋放過期 key 的內存,放到后臺線程執行
lazyfree-lazy-expire?yes
4.?實例內存是否達到上限
把 Redis 當做純緩存使用時,通常會給這個實例設置一個內存上限 maxmemory,然后設置一個數據淘汰策略。
當 Redis 內存達到 maxmemory 后,每次寫入新的數據之前,Redis 必須先從實例中踢出一部分數據,讓整個實例的內存維持在 maxmemory 之下,然后才能把新數據寫進來。
這個踢出舊數據的邏輯也是需要消耗時間的,而具體耗時的長短,要取決于你配置的淘汰策略:
- allkeys-lru:不管 key 是否設置了過期,淘汰最近最少訪問的 key
- volatile-lru:只淘汰最近最少訪問、并設置了過期時間的 key
- allkeys-random:不管 key 是否設置了過期,隨機淘汰 key
- volatile-random:只隨機淘汰設置了過期時間的 key
- allkeys-ttl:不管 key 是否設置了過期,淘汰即將過期的 key
- noeviction:不淘汰任何 key,實例內存達到 maxmeory 后,再寫入新數據直接返回錯誤
- allkeys-lfu:不管 key 是否設置了過期,淘汰訪問頻率最低的 key(4.0 + 版本支持)
- volatile-lfu:只淘汰訪問頻率最低、并設置了過期時間 key(4.0 + 版本支持)
一般最常使用的是 allkeys-lru / volatile-lru 淘汰策略,它們的處理邏輯是,每次從實例中隨機取出一批 key(這個數量可配置),然后淘汰一個最少訪問的 key,之后把剩下的 key 暫存到一個池子中,繼續隨機取一批 key,并與之前池子中的 key 比較,再淘汰一個最少訪問的 key。以此往復,直到實例內存降到 maxmemory 之下。
需要注意的是,Redis 的淘汰數據的邏輯與刪除過期 key 的一樣,也是在命令真正執行之前執行的,也就是說它也會增加我們操作 Redis 的延遲,而且,寫 OPS 越高,延遲也會越明顯。
優化:
(1)淘汰策略改為隨機淘汰,隨機淘汰比 LRU 要快很多(視業務情況調整)
(2)拆分實例,把淘汰 key 的壓力分攤到多個實例上
5. 是否開啟了redis持久化
當 Redis 開啟了后臺 RDB 和 AOF后,在執行時,它們都需要主進程fork出一個子進程進行數據的持久化。主進程在 fork 子進程期間,整個實例阻塞無法處理客戶端請求的時間。因此如果此時磁盤的 IO 負載很高,那這個后臺線程在執行刷盤操作(fsync 系統調用)時就會被阻塞住。此時的主線程依舊會接收寫請求,緊接著,主線程又需要把數據寫到文件內存中(write 系統調用),當主線程使用后臺子線程執行了一次 fsync,需要再次把新接收的操作記錄寫回磁盤時,如果主線程發現上一次的 fsync 還沒有執行完,那么它就會阻塞。
你可以在 Redis 上執行 INFO 命令,查看 latest_fork_usec 項,單位微秒。
##上一次 fork 耗時,單位微秒
latest_fork_usec:59477
對于AOP,還存在著 AOF rewrite操作,這個過程也會占用大量的磁盤 IO 資源。此外fork 的耗時也與系統也有關,虛擬機比物理機耗時更久。
優化:
(1)當子進程在 AOF rewrite 期間,可以讓后臺子線程不執行刷盤
# AOF rewrite 期間,AOF 后臺子線程不進行刷盤操作
# 相當于在這期間,臨時把 appendfsync 設置為了 none
no-appendfsync-on-rewrite?yes
(2)把redis創建到真實物理機上,而不是虛擬機
(3)如果只是把redis作為緩存使用,可以關閉RDB 和 AOF持久化功能
(4)盡量不要把redis 和 其他 i/o 使用率高的創建在同一臺機器上,讓 Redis 運行在獨立的機器上。
(5)SSD磁盤要比機械硬盤讀寫效率高出許多
6. 是否開啟了內存大頁
如果采用了內存大頁,那么,即使客戶端請求只修改 100B 的數據,Redis 也需要拷貝 2MB 的大頁。相反,如果是常規內存頁機制,只用拷貝 4KB。兩者相比,你可以看到,當客戶端請求修改或新寫入數據較多時,內存大頁機制將導致大量的拷貝,這就會影響 Redis 正常的訪存操作,最終導致性能變慢。
首先,我們要先排查下內存大頁。方法是:在 Redis 實例運行的機器上執行如下命令:
$?cat?/sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
如果執行結果是 always,就表明內存大頁機制被啟動了;如果是 never,就表示,內存大頁機制被禁止。
在實際生產環境中部署時,我建議你不要使用內存大頁機制,操作也很簡單,只需要執行下面的命令就可以了:
echo?never /sys/kernel/mm/transparent_hugepage/enabled
其實,操作系統提供的內存大頁機制,其優勢是,可以在一定程序上降低應用程序申請內存的次數。
但是對于 Redis 這種對性能和延遲極其敏感的數據庫來說,我們希望 Redis 在每次申請內存時,耗時盡量短,所以我不建議你在 Redis 機器上開啟這個機制。