动态

详情 返回 返回

Redis key 消失之謎 - 动态 详情

作者:vivo 互聯網存儲團隊 - Lin Haiwen、Xu Xingbao

本文從一次生產環境業務服務報錯,逐步對問題進行定位,深入分析之後發現導致問題的原因,給出相應的優化方法,提升業務可用性。

1分鐘看圖掌握核心觀點👇

圖片

一、問題描述

1.1 報錯信息

應用服務報錯,通過監控日誌發現凌晨2點的時候,應用報錯獲取不到Redis key。

1.2 告警與監控信息

首先想到是否由於內存滿導致的key淘汰,生產的所有Redis都有設置內存告警,但沒有收到內存告警信息;(內存告警需要每隔10秒,連續3次觸發才會告警。)

從監控圖中,可以看到Redis內存稍有增長,但使用率一直偏低,並沒有達到使用率告警。

圖片

查看監控信息:在問題時間點,發生了大量的key過期,初步懷疑是由於key批量設置了過期時間,正好到期了導致大量key失效。

圖片

查看Redis錯誤日誌,沒有發現異常。

二、問題定位

  • 基於前面的監控,初步懷疑是key設置了過期時間導致失效。
  • 是否有上線其他新功能導致?

但是業務反饋不是由於設置過期時間導致;並且第二天問題復現,因此繼續深入定位。

2.1 key是否過期

  • 查看淘汰策略
  • 查看key過期時間

初步判斷確實不是因為key過期導致的大量淘汰,這裏TTL接近5天,但是連着2天出現問題,因此不應該是過期時間到了導致。

xxx:xxx> config get'maxmemory-policy'
1)"maxmemory-policy"
2)"volatile-lru"
xxx:xxx> ttl finance:xxxx_cms_version_10000
-> Redirected to slot [9229] located at xxx:xxx
(integer)387585
xxx:xxx> ttl finance:xxxcms_basic_data_10423
(integer)387574

key是被刪除還是淘汰?查看監控,發現存在key確實被淘汰,説明接下來需要考慮內存問題。

圖片

2.2 是否內存滿了

發現確實短時間內存寫滿了,一開始查看監控由於時間拉的比較長,查看增長趨勢沒發現內存寫滿情況,但是由於沒有達到告警條件,沒有滿足連着3次觸發閾值,故沒有觸發告警。

同時內存用滿問題持續時間較短,約10分鐘左右。

圖片

其他指標檢查,發現出現問題時client_longest_output_list指標存在明顯突刺,該指標非常可疑。

圖片

請求量的大漲,懷疑是請求堆積導致buffer增長使得內存寫滿。但是此時還有疑點:寫入也上漲,是否是由於讀請求積壓導致,還是因為寫入數據導致內存滿?

2.3 找出內存漲的來源

設置定時任務,對出問題時間點前後20分鐘這段時間進行抓包分析。

對比出問題前後幾分鐘的請求,對應時間點請求量飆升,並且請求量來源基本是get請求,set請求也少量增長。

4860 get finance:xxxx__10122
4925 get finance:xxxx__10032
4945set finance:xxxx_data_10013-0
4947 get finance:xxxx_data_10013_cms_version_10000
4976 get finance:xxxx__10251
5054set finance:xxxx__undefined
5098 get finance:xxxx__10018
8729 get finance:xxxx_data_10415_cms_version_10000
9152 get finance:xxxx_data_10401_cms_version_10000
9553 get finance:xxxx_data_10228_cms_version_10000
9597 get finance:xxxx_data_10213_cms_version_10000
9622 get finance:xxxx_data_10032_cms_version_10000
9647 get finance:xxxx_data_10347_cms_version_10000
9674 get finance:xxxx_data_10182_cms_version_10000
9742 get finance:xxxx_data_10251_cms_version_10000
10085 get finance:xxxx_data_10019_cms_version_10000
23064 get finance:xxxx__10423
45176 get finance:xxxx_data_10423_cms_version_10000 

[root@db-prd-xxx.v-bj-3.vivo.lan:/data/redis/scpdir]

# cat 0807.cap | grep '2024-08-07 01:59'|awk '{print $8,$10}'|sort |uniq -c |sort -k 1 -n

同時也對set的內容進行分析,發現set的數據並不足使內存寫滿;且上面監控可以看到,內存寫滿問題持續時間很短,因此應該不是數據增長導致。

進一步對get請求來源分析:

結合 IP來源以及 keyname;跟業務同學溝通確認:

由於業務讀請求量大漲導致,業務請求從每分鐘27w左右上漲到70w左右,主要有:

2.4 機制分析

內存用量上漲超限會觸發Redis節點基於已經配置的volatile-lru策略進行過期數據淘汰,所以需要找到內存上漲的來源。基於監控指標排查分析,基本確定了發生異常期間沒有集中的大量數據寫入,反而發現大量網絡數據的輸出,抓包也印證了此時節點主要是在處理get命令讀請求。進一步地發現了client-output-buffer-limit這個指標出現異常上漲,才最終鎖定到Redis的回包積壓問題。

關於Redis的結果回包邏輯,首先要了解Redis的結果存儲空間設計。Redis針對每一個連接客户端都會初始化一個大小為16K的靜態的buf區域和一個空的鏈表結果,相關聲明代碼如下:

#define REDIS_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
char buf[REDIS_REPLY_CHUNK_BYTES];
list *reply;
c->reply = listCreate();

對於Redis而言正常執行的命令都會有數據回包,而回包結果都需要在客户端中做暫存。Redis是如何結合以上兩個數據進行結果存儲的呢?主要邏輯是如果靜態buf區域能夠滿足回包結果存儲,即結果不大於16k,那麼結果就會存儲靜態buf中,將結果不斷插入reply鏈表中;同時我們都知道Redis支持豐富的數據類型和操作命令,有些批量數據讀取命令的結果可能會有很多字段,這些字段也會作為一個個鏈表元素追加到reply鏈表中。正常情況下,我們在Redis節點上執行info clients命令可以獲得如下客户端的統計信息:

 > info clients 
 # Clients 
 connected_clients:  1
 client_longest_output_list:  0
 client_biggest_input_buf:  0
 blocked_clients:  

其中的client_longest_output_list字段代表此時節點的所有連接客户端中回包結果的緩存情況。

但是按照之前服務監控和抓包結果分析,具體執行的指令都是get,實際數據也沒有超過16k大小,並沒有滿足將結果存儲到鏈表的條件。這裏有個經常被忽略的場景,就是Redis其實支持pipeline命令執行方式的,簡單來説就是Redis支持一次性接收一個客户端的多個指令,具體執行過程中會把這些指令的結果不斷暫存,然後在後續流程中集中回包,如果這時候不能及時地把數據通過網絡發出去,就有可能出現reply鏈表長度激增的現象,進而導致客户端佔用內存激增,這正是我們本次遇到的場景。

圖片

Redis的maxmemory參數限制的是Redis實例可以使用的最大內存,這部分內存主要包括以下幾個部分:業務數據佔用的內存、客户端連接的輸入/輸出緩衝區、複製積壓緩衝區、AOF 緩衝區以及其他一些內部開銷。

具體來説,Redis 的maxmemory 限制包括:

  1. 業務數據佔用的內存,這部分是用户在Redis中存儲的數據。
  2. 客户端連接的輸入/輸出緩衝區,用於暫存客户端發送的命令和Redis 返回給客户端的數據。
  3. 複製積壓緩衝區:用於主從複製,當從節點斷線重連時,可以從這個緩衝區拉取丟失的數據。
  4. AOF 緩衝區:用於持久化,當開啓AOF 持久化時,Redis 會將寫操作追加到AOF 緩衝區,然後異步地寫入AOF 文件。
  5. 其他內部開銷:包括Redis 進程本身的一些數據結構、對象、碎片內存等。

因此,在設置Redis 的maxmemory 參數時,需要綜合考慮業務數據的內存佔用、各個緩衝區的大小以及內存碎片率等因素,合理地分配內存,避免出現內存溢出或性能下降的問題。

三、問題解決

3.1 緊急修復

  • 臨時擴大Redis集羣內存,避免內存寫滿。
  • 限制client-output-buffer-limit大小,避免由於請求過大導致內存突增。
  • 業務限流\&削峯,避免請求量突增。
  • 增加兜底機制,如果由於Redis key被淘汰,則去MySQL查詢,避免業務直接報錯。

3.2 根本解決

業務進行業務邏輯優化,將請求打散,避免同一時間集中大量請求Redis。

四、總結

本次問題是由於業務集中請求Redis,導致緩存積壓內存增長達到最大內存限制,觸發Redis淘汰策略對key進行驅逐。key被淘汰丟失後,需要增加兜底機制去DB側請求避免業務報錯。雖然Redis性能比較好,但是也要儘量打散請求,合理評估存儲側的性能。

同時,對於Redis淘汰策略,對於數據比較重要的集羣,可以考慮使用不驅逐的方式。合理設置TTL保留時間,把Redis的內存使用率保持在安全的區域。

user avatar freeman_tian 头像 huikaichedemianbao 头像 jianweilai 头像 kohler21 头像 assassin 头像 Asp1rant 头像 jkdataapi 头像 laggage 头像 tangzhiyuan 头像 sy_records 头像 hppyvyv6 头像 god23bin 头像
点赞 29 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.