當一個較大的key存在時,持續新增,key所占內存會越來越大,嚴重時會導致內存數據溢出;當key過期需要刪除時,由于數據量過大,可能發生主庫較響應時間過長,主從數據同步異常(刪除掉的數據,從庫還在使用)。
問題的嚴重性
首先,要申明一下,問題的嚴重性。
BigKey(大key)和HotKey(熱key)的問題是較常見。
這類問題不止會使服務的性能下降,還會影響用戶正常使用功能,
甚至會造成大范圍的服務故障,故障有時還會發生連環效應,導致更加嚴重的后果,發生系統的雪崩,造成巨大的經濟損失,巨大的品牌損傷。
所以,在 redis 運維過程中,由于 Bigkey 的存在,DBA 也一直和業務開發方強調 Bigkey 的規避方法以及危害。
在開發的過程中,開發同學,也需要十分重視和預防這個問題。
一、什么是BigKey、HotKey?
什么是BigKey
俗稱“大key”,是指redis在日常生產的過程中,某些key所占內存空間過大。
通俗來說,redis是key-value的存儲方式,當一個key所對應的存儲數值達到一定程度,就會出現大key的情況。
redis里有多種數據存儲結構,如String、List、Hash等,每種存儲結構都有能夠承載的數據限值。當一個key包含的內容接近限制,或者高于平均值,大key就產生了。關注公眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部JAVA性能調優手冊
在 Redis 中,一個字符串類型最大可以到 512MB,一個二級數據結構(比如 hash、list、set、zset 等)可以存儲大約 40 億個(2^32-1)個元素,
但實際上不會達到這么大的值,一般情況下如果達到下面的情況,就可以認為它是 Bigkey 了。
- 【字符串類型】:單個 string 類型的 value 值超過 1MB,就可以認為是 Bigkey。
- 【非字符串類型】:哈希、列表、集合、有序集合等, 它們的元素個數超過 2000 個,就可以認為是 Bigkey。
什么是HotKey
俗稱“熱key”,一個key對應在一個redis分片上,當短時間內大量的請求打到該分片上,key被頻繁訪問,該key就是熱key。
當大量的請求,經過分發和計算,最終集中于同一個redis實例下的某個key時,該key由于被請求頻率過高,而占用掉了大量資源。
而其他分片,由于key的不合理分配導致請求量較少。
整個redis集群呈現出了資源使用不均衡的現象。
舉個例子:一線女明星官宣領證結婚,短時間內該女星微博賬號被訪問量激增(假設該賬號內容被同步在緩存,賬號id作為key),微博服務癱瘓(不具備任何實時參考性,僅作為虛擬的例子)。
在該場景下,上述key被大量訪問,造成熱key。
總之,在某個Key接收到的訪問次數、顯著高于其它Key時,我們可以將其稱之為HotKey,
從訪問量上來說,常見的HotKey如:
- 某Redis實例的每秒總訪問量為10000,而其中一個Key的每秒訪問量達到了7000(訪問次數顯著高于其它Key)
- 對一個擁有上千個成員且總大小為1MB的HASH Key每秒發送大量的HGETALL(帶寬占用顯著高于其它Key)
- 對一個擁有數萬個成員的ZSET Key每秒發送大量的ZRANGE(CPU時間占用顯著高于其它Key)
二、服務中的bigkey和hotkey
會導致什么問題
我們可以通過上述兩種key的特性,來簡單分析可能出現的幾種問題。
第1:bigkey的問題
主要的問題是一個key所占空間太大,內存空間分配不均衡(小tips:redis是內存型key-value數據庫)。那就可能引發以下問題:
1.數據請求大量超時:
redis是單線程的,當一個key數據響應的久一點,就會造成后續請求頻繁超時。如果服務容災措施考慮得不夠,會引發更大的問題。
2.侵占帶寬網絡擁堵:
當一個key所占空間過大,多次請求就會占用較大的帶寬,直接影響服務的正常運行。
3.內存溢出或處理阻塞:
當一個較大的key存在時,持續新增,key所占內存會越來越大,嚴重時會導致內存數據溢出;當key過期需要刪除時,由于數據量過大,可能發生主庫較響應時間過長,主從數據同步異常(刪除掉的數據,從庫還在使用)。
第2:hotkey的問題
熱key,熱key的問題是單點訪問頻率過高。那就可能引發以下問題:
1.分片服務癱瘓:
redis集群會分很多個分片,每個分片有其要處理的數據范圍。當某一個分片被頻繁請求,該分片服務就可能會癱瘓。
2.Redis 分布式集群優勢弱化:
如果請求不夠均衡,過于單點,那么redis分布式集群的優勢也必然被弱化。
3.可能造成資損:
在極端場景下,容易發生邊界數據處理不及時,在訂單等場景下,可能造成資損。
4.引發緩存擊穿:
我們都知道,當緩存請求不到,就會去請求數據庫。如果請求過于集中,redis承載不了,就會有大量請求打到數據庫。此時,可能引發數據庫服務癱瘓。進而引發系統雪崩。
5.cpu占用高,影響其他服務:
單個分片cpu占用率過高,其他分片無法擁有cpu資源,從而被影響。
三、如何發現bigkey和hotkey
1.業務分析結合技術方案:
通常需要對業務場景做分析,結合技術方案去判斷是否會出現大key、熱key的問題。
比如說:
(1)購物車場景,當一個購物車的key設計,沒有上限,沒有其他隨機值約束,僅使用了mid。這個時候就要注意,如果有個購物狂,一次加購5w件商品怎么辦?
(2)活動資格列表場景,當一個活動的資格查詢list被放入一個key,活動期間頻繁的查詢和操作。這個時候就要注意,list的數據量有多少?查詢資格的操作是否集中?如果集中,qps是多少?
2.借助redis命令來發現:
Redis4.0 及以上版本提供了--Bigkeys, --hotkeys 命令,可以分析出實例中每種數據結構的 top 1 的 Bigkey,同時給出了每種數據類型的鍵值個數以及平均大小。
查看bigkey:redis-cli -a 登錄密碼 --bigkeys
查看hotkey:redis-cli -a 登錄密碼 --hotkeys
--bigkey 的使用示例
3.借助工具:
(1)可使用redis可視化工具進行查看(例如:another redis desktop manager)
可視化的工具可以明確給出redis集群當下的信息,經過簡要數據分析,便可觀測異常。
(2)借助市面上的開源工具(本文暫不對此深入探討)
redis-rdb-tools(附:https://github.com/sripathikrishnan/redis-rdb-tools)
(3)借助公司的自研工具
如果 vivo內部的 DaaS(數據即服務)平臺。
4.RDB 文件分析法
通過 RDB 文件,分析 big key
四、如何解決bigkey和hotkey問題
解決方案
bigkey的解決方案
主要的方法:對 big key 進行拆分
對 big key 存儲的數據 (big value)進行拆分,變成value1,value2… valueN,
如果big value 是個大json 通過 mset 的方式,將這個 key 的內容打散到各個實例中,減小big key 對數據量傾斜造成的影響。關注公眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部Java性能調優手冊
如果big value 是個大list,可以拆成將list拆成。= list_1, list_2, list3, listN
其他數據類型同理
hotkey的解決方案
主要的方法:使用本地緩存
在 client 端使用本地緩存,從而降低了redis集群對hot key的訪問量,
但是本地緩存 ,帶來兩個問題:1、如果對可能成為 hot key 的 key 都進行本地緩存,那么本地緩存是否會過大,從而影響應用程序本身所需的緩存開銷。
2、如何保證本地緩存和redis集群數據的有效期的一致性。
以上兩個問題,具體看:聊聊 Redis+Caffeine 兩級緩存
五、生產實例:
以下是vivo團隊Bigkey問題的解決方案
此生產實例,非常寶貴,是珍貴的一線工業級實操案例,來源于 vivo 互聯網數據庫團隊- Du Ting
陳某僅僅是將其結構,做進一步的梳理,方便大家學習。
vivo 團隊運維的Redis集群的介紹
全網 Redis 集群有 2200 個以上,實例數量達到 4.5 萬以上,
進行一次全網 Bigkey 檢查,估計需要以年為時間單位,非常耗時。
Bigkey的來源
Bigkey 一般都是由于程序設計不當、或者對于數據規模 預估 失策,比如以下的情況。
- 【統計】場景遇到一個統計類 key 記錄訪問用戶的 IP,隨著網站訪問的用戶越來越多,這個 key 的元素會越來越大,形成 Bigkey。
- 【緩存】場景CacheAside 模式,業務程序 將數據從數據庫查詢出來序列化放到 Redis 里,就會查詢數據庫并將查詢到的數據追加到 Redis 緩存中,短時間內會緩存大量的數據到 Redis 的 key 中,形成 Bigkey。
- 【隊列】場景把 Redis 當做隊列使用場景,如果消費不及時,將導致隊列越來越大,出現 Bigkey。
問題1:內存空間不均勻
內存空間不均勻會不利于集群對內存的統一管理,有數據丟失風險。
下圖中的三個節點是同屬于一個集群,它們的 key 的數量比較接近,但內存容量相差比較多,存在 Bigkey 的實例占用的內存多了 4G 以上了。
可以使用 Daas 平臺“工具集-操作項管理”,選擇對應的 slave 實例執行分析,找出具體的 Bigkey。
問題2:超時阻塞
Redis 是單線程工作的,通俗點講就是同一時間只能處理一個 Redis 的訪問命令,
操作 Bigkey 的命令通常比較耗時,這段時間 Redis 不能處理其他命令,其他命令只能阻塞等待,這樣會造成客戶端阻塞,導致客戶端訪問超時,更嚴重的會造成 master-slave 的故障切換。
當然,造成阻塞的操作不僅僅是業務程序的訪問,還有 key 的自動過期的刪除、del 刪除命令,對于 Bigkey,這些操作也需要謹慎使用。
來一個生產上的超時阻塞案例
我們遇到一個這樣超時阻塞的案例,業務方反映:程序訪問 Redis 集群出現超時現象,hkeys 訪問 Redis 的平均響應時間在 200 毫秒左右,最大響應時間達到了 500 毫秒以上,如下圖。
hkeys 是獲取所有哈希表中的字段的命令,分析應該是集群中某些實例存在 hash 類型的 Bigkey,導致 hkeys 命令執行時間過長,發生了阻塞現象。
1.使用 Daas 平臺“服務監控-數據庫實例監控”,選擇 master 節點,選擇 Redis 響應時間監控指標“redis.instance.latency.max”,如下圖所示,從監控圖中我們可以看到
(1)正常情況下,該實例的響應時間在 0.1 毫秒左右。
(2)監控指標上面有很多突刺,該實例的響應時間到了 70 毫秒左右,最大到了 100 毫秒左右,這種情況就是該實例會有 100 毫秒都在處理 Bigkey 的訪問命令,不能處理其他命令。
通過查看監控指標,驗證了我們分析是正確的,是這些監控指標的突刺造成了 hkeys 命令的響應時間比較大,我們找到了具體的 master 實例,然后使用 master 實例的 slave 去分析下 Bigkey 情況。
2.使用 Daas 平臺“工具集-操作項管理”,選擇 slave 實例執行分析,分析結果如下圖,有一個 hash 類型 key 有 12102218 個 fields。
- 和業務溝通,進行Bigkey 拆分這個 Bigkey 是連續存放了 30 天的業務數據了,建議根據二次 hash 方式拆分成多個 key,也可把 30 天的數據根據分鐘級別拆分成多個 key,把每個 key 的元素數量控制在 5000 以內。優化后,監控指標的響應時間的突刺就會消失了。
問題3:網絡阻塞
Bigkey 的 value 比較大,也意味著每次獲取要產生的網絡流量較大,假設一個 Bigkey 為 10MB,客戶端每秒訪問量為 100,那么每秒產生 1000MB 的流量,對于普通的千兆網卡(按照字節算是 128MB/s)的服務器來說簡直是滅頂之災。
而且我們現在的 Redis 服務器是采用單機多實例的方式來部署 Redis 實例的,也就是說一個 Bigkey 可能會對同一個服務器上的其他 Redis 集群實例造成影響,影響到其他的業務。
問題4:遷移困難
我們在運維中經常做的變更操作是水平擴容,就是增加 Redis 集群的節點數量來達到擴容的目的,這個水平擴容操作就會涉及到 key 的遷移,把原實例上的 key 遷移到新擴容的實例上。
當要對 key 進行遷移時,是通過 migrate 命令來完成的,migrate 實際上是通過 dump + restore + del 三個命令組合成原子命令完成,它在執行的時候會阻塞進行遷移的兩個實例,直到以下任意結果發生才會釋放:遷移成功,遷移失敗,等待超時。
如果 key 的遷移過程中遇到 Bigkey,會長時間阻塞進行遷移的兩個實例,可能造成客戶端阻塞,導致客戶端訪問超時;也可能遷移時間太長,造成遷移超時導致遷移失敗,水平擴容失敗。
來一個生產上的遷移失敗案例
我們也遇到過一些因為 Bigkey 擴容遷移失敗的案例,如下圖所示,
這是一個 Redis 集群水平擴容的工單,需要進行 key 的遷移,當工單執行到 60%的時候,遷移失敗了。
如何解決呢?
大概的解決流程,如下:
- 進入工單找到失敗的實例,使用失敗實例的 slave 節點,在 Daas 平臺的“工具集-操作項管理”進行 Bigkey 分析。
- 經過分析找出了 hash 類型的 Bigkey 有 8421874 個 fields,正是這個 Bigkey 導致遷移時間太長,超過了遷移時間限制,導致工單失敗了。
3.和業務溝通,這些 key 是記錄用戶訪問系統的某個功能模塊的 ip 地址的,訪問該功能模塊的所有 ip 都會記錄到給 key 里面,隨著時間的積累,這個 key 變的越來越大。同樣是采用拆分的方式進行優化,可以考慮按照時間日期維度來拆分,就是一段時間段的訪問 ip 記錄到一個 key 中。
4.Bigkey 優化后,擴容的工單可以重試,完成集群擴容操作。
生產上如何進行Bigkey 的發現
- 首先需要重源頭治理,防止 Bigkey 的產生;
- 其次是需要能夠及時的發現,發現后及時處理。
分析 Bigkey 的方法不少,這里介紹兩種比較常用的方法,也是 Daas 平臺分析 Bigkey 使用的兩種方式,分別是 Bigkeys 命令分析法、RDB 文件分析法。
1.Bigkeys 命令分析
Redis4.0 及以上版本提供了--Bigkeys 命令,可以分析出實例中每種數據結構的 top 1 的 Bigkey,同時給出了每種數據類型的鍵值個數以及平均大小。
執行--Bigkeys 命令時候需要注意以下幾點:
- 建議在 slave 節點執行,因為--Bigkeys 也是通過 scan 完成的,可能會對節點造成阻塞。
- 建議在節點本機執行,這樣可以減少網絡開銷。
- 如果沒有從節點,可以使用--i 參數,例如(--i 0.1 代表 100 毫秒執行一次)。
- --Bigkeys 只能計算每種數據結構的 top1,如果有些數據結構有比較多的 Bigkey,是查找不出來的。
Daas 平臺集成了基于原生--Bigkeys 代碼實現的查詢 Bigkey 的方式,這個方式的缺點是只能計算每種數據結構的 top1,如果有些數據結構有比較多的 Bigkey,是查找不出來的。該方式相對比較安全,已經開放出來給業務開發同學使用。
2. RDB 文件分析
借助開源的工具,比如 rdb-tools,分析 Redis 實例的 RDB 文件,找出其中的 Bigkey,這種方式需要生成 RDB 文件,需要注意以下幾點:
- 建議在 slave 節點執行,因為生成 RDB 文件會影響節點性能。
- 需要生成 RDB 文件,會影響節點性能,雖然在 slave 節點執行,但是也是有可能造成主從中斷,進而影響到 master 節點。
Daas 平臺集成了基于 RDB 文件分析代碼實現的查詢 Bigkey 的方式,可以根據實際需求自定義填寫 N,分析的 top N 個 Bigkey。該方式相對有一定風險,只有 DBA 有權限執行分析。
3.Bigkey 巡檢
通過巡檢,可以暴露出隱患,提前解決,避免故障的發生,進行全網 Bigkey 的巡檢,是避免 Bigkey 故障的比較好的方法。
由于全網 Redis 實例數量非常大,分析的速度比較慢,使用當前的分析方法很難完成。
為了解決這個問題,存儲研發組分布式數據庫同學計劃開發一個高效的 RDB 解析工具,然后通過大規模解析 RDB 文件來分析 Bigkey,可以提高分析速度,實現 Bigkey 的巡檢。
生產上 Bigkey 處理優化
1. Bigkey 拆分
優化 Bigkey 的原則就是 string 減少字符串長度,list、hash、set、zset 等減少元素數量。當我們知道哪些 key 是 Bigkey 時,可以把單個 key 拆分成多個 key,比如以下拆分方式可以參考。
- big list:list1、list2、...listN
- big hash:可以做二次的 hash,例如 hash%100
- 按照日期拆分多個:key20220310、key20220311、key202203212
2. Bigkey 分析工具優化
我們全網 Redis 集群有 2200 以上,實例數量達到 4.5 萬以上,有的比較大的集群的實例數量達到了 1000 以上,前面提到的兩種 Bigkey 分析工具還都是實例維度分析,對于實例數量比較大的集群,進行全集群分析也是比較耗時的,為了提高分析效率,從以下幾個方面進行優化:
- 可以從集群維度選擇全部 slave 進行分析。
- 同一個集群的相同服務器 slave 實例串行分析,不同服務器的 slave 實例并行分析,最大并發度默認 10,同時可以分析 10 個實例,并且可以自定義輸入執行分析的并發度。
- 分析出符合 Bigkey 規定標準的所有 key 信息:大于 1MB 的 string 類型的所有 key,如果不存在就列出最大的 50 個 key;hash、list、set、zset 等類型元素個數大于 2000 的所有 key,如不存在就給出每種類型最大的 50 個 key。
- 增加暫停、重新開始、結束功能,暫停分析后可以重新開始。
水平擴容遷移優化
目前情況,我們有一些 Bigkey 的發現是被動的,一些是在水平擴容時候發現的,由于 Bigkey 的存在導致擴容失敗了,嚴重的還觸發了 master-slave 的故障切換,這個時候可能已經造成業務程序訪問超時,導致了可用性下降。
我們分析了 Daas 平臺的水平擴容時遷移 key 的過程及影響參數,內容如下:
(1)【cluster-node-timeout】:控制集群的節點切換參數,
master 堵塞超過 cluster-node-timeout/2 這個時間,就會主觀判定該節點下線 pfail 狀態,如果遷移 Bigkey 阻塞時間超過 cluster-node-timeout/2,就可能會導致 master-slave 發生切換。
(2)【migrate timeout】:控制遷移 io 的超時時間
超過這個時間遷移沒有完成,遷移就會中斷。
(3)【遷移重試周期】:遷移的重試周期是由水平擴容的節點數決定的,
比如一個集群擴容 10 個節點,遷移失敗后的重試周期就是 10 次。
(4)【一個遷移重試周期內的重試次數】:在一個起遷移重試周期內,會有 3 次重試遷移,
每一次的 migrate timeout 的時間分別是 10 秒、20 秒、30 秒,每次重試之間無間隔。
比如一個集群擴容 10 個節點,遷移時候遇到一個 Bigkey,第一次遷移的 migrate timeout 是 10 秒,10 秒后沒有完成遷移,就會設置 migrate timeout 為 20 秒重試,如果再次失敗,會設置 migrate timeout 為 30 秒重試,
如果還是失敗,程序會遷移其他新 9 個的節點,但是每次在遷移其他新的節點之前還會分別設置 migrate timeout 為 10 秒、20 秒、30 秒重試遷移那個遷移失敗的 Bigkey。
這個重試過程,每個重試周期阻塞(10+20+30)秒,會重試 10 個周期,共阻塞 600 秒。其實后面的 9 個重試周期都是無用的,每次重試之間沒有間隔,會連續阻塞了 Redis 實例。
(5)【遷移失敗日志】:日志的缺失
遷移失敗后,記錄的日志沒有包括遷移節點、solt、key 信息,不能根據日志立即定位到問題 key。
我們對這個遷移過程做了優化,具體如下:
(1)【cluster-node-timeout】:延長超時時間
默認是 60 秒,在遷移之前設置為 15 分鐘,防止由于遷移 Bigkey 阻塞導致 master-slave 故障切換。
(2)【migrate timeout】:減少阻塞時間
為了最大限度減少實例阻塞時間,每次重試的超時時間都是 10 秒,3 次重試之間間隔 30 秒,這樣最多只會連續阻塞 Redis 實例 10 秒。
(3)【重試次數】:去掉了其他節點遷移的重試
遷移失敗后,只重試 3 次(重試是為了避免網絡抖動等原因造成的遷移失敗),每次重試間隔 30 秒,重試 3 次后都失敗了,會暫停遷移,日志記錄下 Bigkey,去掉了其他節點遷移的重試。
(4)【優化日志記錄】:日志記錄
遷移失敗日志記錄遷移節點、solt、key 信息,可以立即定位到問題節點及 key。
關于BigKey、Hotkey的總結
首先是需要從源頭治理,防止 Bigkey 、Hotkey形成,加強對業務開發同學 bigkey 相關問題的宣導;
其次,提升及時發現的能力,實現 Bigkey 、Hotkey 及時探測能力。
參考資料:
Github:rdb-tools:https://github.com/sripathikrishnan/redis-rdb-tools
(1) redis命令:Redis 命令參考 — Redis 命令參考
(2) Github: https://github.com/sripathikrishnan/redis-rdb-tools
(3) another redis desktop manager下載地址AnotherRedisDesktopManager 發行版:https://gitee.com/qishibo/AnotherRedisDesktopManager/releases