RedisTemplate的踩坑現場

在這裏插入圖片描述

張偉帆(網易嚴選技術團隊)node

RedisTemplate是Spring封裝的一個最經常使用的redis數據訪問工具類,功能強大,使用簡潔。但在RedisTemplate看似簡單的API下,也是暗流涌動,背後別有一番洞天。本文從一個線上性能問題入手,抽絲剝繭一步步探索RedisTemplate,挖出了RedisTemplate操做psetex命令的深坑。git



1、背景

在以前的壓測過程當中,商詳頁面RT居高不下,發現yx_malltech_user這個redis集羣的異常報警,起初是在業務沒有開啓分流讀的狀況下,一個Redis集羣slave節點存在大量鏈接,佔用大量CPU資源,且致使redis主從斷掉等異常行爲。github

dba分析後發現,從數據庫實時監控平臺,能夠看到有大量的cluster命令產生,且這個請求一直存在,只是壓測流量過大,致使問題放大,單個cluster命令請求都是在30毫秒以上。都是打到隨機一個NODE節點上,致使這個節點cpu飆升。redis

而後DBA開始相應的處理,包括但不限於單獨隔離異常節點到獨立機器上,配置參數優化等,問題沒有根本解決。能夠肯定不是Redis服務端問題,應該是業務代碼端問題,須要從業務用法開始排查使用方式,看是如何觸發了大量的cluster命令。spring

6.9單壓商詳發現問題仍是存在。開始咱們的排查之路。數據庫


2、服務信息

  • 涉及服務:yanxuan-app
  • 涉及場景:商詳
  • spring版本:4.0.8
  • spring-data-redis版本:1.8.4.RELEASE
  • jedis版本:2.9.0

3、分析過程

1. 監控觀察

既然是cluster命令異常過多,咱們觀察下監控數據。 6.9號下午對於商詳的單壓緩存

在這裏插入圖片描述 能夠看到cluster命令確實過多,且耗時較長。從而影響其餘正常get,set操做。markdown

咱們仔細觀察,發現異常的請求不只僅是cluster,仍是psetex。正常來說,咱們操做緩存並不會直接使用psetex命令。單獨觀察下二者。app

下面兩張圖分別是 psetex命令與cluster命令的走勢圖,發現是一致的。基本能夠肯定psetex和cluster是有強關聯關係的。框架

pset以下 在這裏插入圖片描述 cluster以下 在這裏插入圖片描述

且cluster只打到 10.130.68.239:16379 這一臺機器上。 在這裏插入圖片描述

2. 代碼分析

從上面觀察獲得,psetex命令和cluster命令基本同比例,且psetex命令請求量很多。而咱們開發中,實際上並不會使用pset,咱們都是redisTemplate.opsForValue().set()

這一點很奇怪,因此咱們開始擼下代碼。

在這裏插入圖片描述

這裏面封裝了緩存邏輯,底層就是redisTemplate.opsForValue().set()方法,那咱們進去redisTemplate源碼看下,它究竟偷偷作了啥。 在這裏插入圖片描述 在這個代碼裏面,咱們就明白了psetex哪裏來的。若是你timeout使用的時間單元是ms,就會使用psetex,而其餘就使用setex命令。感受咱們往前走了一步。可是psetex和cluster命令同比例爲啥呢?繼續看下。 在這裏插入圖片描述 這裏面會根據key去計算對應的槽所在節點。咱們再進去topology方法裏瞧瞧。 在這裏插入圖片描述 看到這段代碼,真相就在眼前了。原來獲取集羣拓撲信息有一個緩存,失效時間100ms(這裏吐槽下,spring竟然把緩存過時時間寫死了)。緩存失效,而後從cluster的clusternodes循環取節點去發送cluster nodes命令來更新拓撲信息。這裏就證實了cluste命令與psetex命令之間存在的關聯性了。緩存過時時間是100ms,必然引發頻繁的發起cluster nodes命令。經過和dba大大峯哥確認,確實在壓測過程當中存在的大量cluster命令,就是cluster nodes命令。驗證了猜測。

四. 解決方法與驗證


1. 解決方法

解決方法很簡單:將設置緩存中使用毫秒做爲過時時間的代碼全都改爲按秒級別來設置緩存。

只要改爲秒級別,也就是使用setex,setex不會觸發cluster命令。避免使用psetex命令,帶來的頻繁cluster nodes命令。

2. 驗證

改完上線後,觀察監控。發如今上線過程當中,明顯有降低的趨勢。 在這裏插入圖片描述 在平峯期表現,已經降到很是低了 在這裏插入圖片描述 最終的壓測驗證: 在這裏插入圖片描述 從壓測結果來看,表現也是很是良好。問題得以解決。

核心鏈路壓測中,總體商詳的MRT也從以前100ms+降到66ms。


五. 總結與思考


1. 總結

這個問題比較有意思,罪魁禍首竟然是一個時間單元。在於使用了 1000ms 和 1s 理論上應該是同樣的,絕大部分人並不會去關心。然而spring底層留下了這麼一個用法上的坑,致使了問題在大流量壓測下的出現。一個小小的區別帶來了巨大的影響。

而咱們對於緩存的過時時間其實不必精確到毫秒級別的,因此直接改爲秒級別便可。

2. 思考

PSETEX和SETEX除了過時時間的精度問題,並無本質的區別。

因此spring提供了根據時間單元來區別使用setex和psetex,而接口沒有顯示體現。我我的以爲這是spring接口設計一個不合理的地方。雖然是爲了方便開發者要屏蔽細節,但是底層的邏輯並非徹底同樣(不是隻是發送不一樣命令而已)。而是有獲取cluster nodes等命令邏輯,是兩套邏輯。對於咱們開發設計暴露出去的接口和方法也是一種警醒。一個接口,方法應該保證用戶便於理解,不能有二義性。不然就得拆分多個接口,讓用戶選擇,不給用戶留坑。

後續優化措施:

  • 梳理出一個版本的最佳實踐,而後規範框架的使用版本。好比spring-data-redis1.x版本就統一使用1.8.4,而後有對應的最佳實踐;2.x版本就統一使用2.2.4,也有對應的最佳實踐。這樣子能夠避免踩重複的坑,能站在巨人的肩膀上看的更遠。
  • 創建規範的CodeReview機制。經過多人的代碼review以及不一樣人的經驗,可能發現一些隱藏的坑。避免上線後出現問題。

六. 番外


1. 爲何cluster命令會老是打到某一個節點上呢?

前面咱們說到,還有一個問題點,cluster命令老是打到某一臺節點上,致使這個節點cpu使用率飆升。

緣由仍是出在下面Spring-data-redis的這段代碼上:

在這裏插入圖片描述 for循環遍歷全部節點,從第一個開始發送nodes命令,若是獲得返回,就直接返回了。而咱們節點信息通常狀況下都是不變的。所以entrySet的順序也不會改變,Cluster Nodes命令就會一直髮送到同一個節點上。

在網上資料查到,這個bug已經官方修復了。jira.spring.io/browse/DATA…

2. Cluster nodes命令爲啥會很慢呢?

嗯,這個就是直接摘抄網上資料了。

答案在下圖,CLUSTER_SLOTS常量等於16384,所以redis每次都要循環不少次去組裝每一個節點的slot信息。CPU至少須要循環16384乘以N次,N爲redis集羣master的個數。所以,隨着redis集羣規模的擴大、以及客戶端節點數的增長,NODES命令打滿CPU的問題會愈來愈嚴重。

redis系統命令NODES的性能問題,在2018年已經有反饋給redis官方:github.com/antirez/red… 在這裏插入圖片描述


3. PSETEX命令的使用場景?

redis.io/commands/ps…

PSETEX works exactly like SETEX with the sole difference that the expire time is specified in milliseconds instead of seconds.

PSETEX和SETEX除了過時時間的精度問題,並無本質的區別。 因此我我的理解,psetex應該是用在對於過時時間的精度要求較高的場景吧。

這次問題的定位排查,感謝dba大大在服務端作了大量的優化,且提供了很是好的分析方向,才能很快且明確的從業務代碼上進行排查。在業務代碼的排查過程當中,離不開文淵。十分感謝!!!


網易技術熱愛者隊伍持續招募隊友中!網易嚴選,由於熱愛因此選擇, 期待志同道合的你加入咱們, Java開發簡歷可發送至郵箱:lizhuo01@corp.netease.com

相關文章
相關標籤/搜索