一次Redis TTL 爲0的問題排查

原文鏈接:http://blog.sina.com.cn/s/blog_6c9df8a30102vc9d.html

一次Redis TTL 爲0的問題排查

 


事情是這樣的,今天中午業務突然RTX上找我,說一個新建的Twemproxy集羣數據查詢的時候出了問題,Redis的TTL返回爲0,讓我幫忙看一看:

 當時聽完就覺得問題很詭異,按照之前的經驗來說,Redis的TTL怎麼也不可能爲0啊,見:http://redis.io/commands/ttl

一次Redis <wbr>TTL <wbr>爲0的問題排查

 Redis的key,通過TTL命令返回key的過期時間,一般來說有3中:

1.   當前key沒有設置過期時間,所以會返回-1.

2.   當前key有設置過期時間,而且key已經過期,所以會返回-2.

3.   當前key有設置過期時間,且key還沒有過期,故會返回key的正常剩餘時間.

所以,十分疑惑爲何會出現key的TTL爲0的情況,當時第一感覺問題會不會出現在Twemproxy裏面,於是讓複雜源碼開發的同事查一下twemproxy中是否有對ttl命令的二次處理,於此同時登錄到那臺twemproxy上,ttl查看相關key,確認結果確實爲0,如下圖所示:

一次Redis <wbr>TTL <wbr>爲0的問題排查

遇到這種問題,首選懷疑是否是個例,於是自行插入key測試:

一次Redis <wbr>TTL <wbr>爲0的問題排查

 

 測試過程如上圖所示:

1.   setex a 10 1;設置一個key a,過期時間10s,值爲1.

2.   通過TTL命令查看a的剩餘過期時間,結果爲6s.

3.   等待一會兒,再次TTL查看,key的過期時間竟然爲0。

果然不是個別現象。同時源碼的同事反饋,twemproxy本身並未對ttl命令做過任何處理,故我們通過內部的find_key工具,獲取該key所在的hash環上的real server(一致性hash算法),到所在的redis再確認一下:

一次Redis <wbr>TTL <wbr>爲0的問題排查

        看來確實是redis本事的問題,我們開始懷疑是Redis的內部出現的bug,於是在其他版本上進行了測試,返回的結果都是正確的,看來版本bug的可能性很高,但是並不能確定。

   我們又在其他的同版本實例上, 進行了同樣的測試,但是卻並未發現TTL返回0的情況。看來只能去查看源碼了。

   於是我們查看了redis對於ttl這個命令的源代碼,代碼如下:

一次Redis <wbr>TTL <wbr>爲0的問題排查

    代碼中確實出現了TTL = 0 的情況,理論上對於存在過期時間的key,應該返回-2纔對,而這個代碼中,第一個if語句(應該返回-2)並沒有執行,才導致調入了第二個循環裏,而理論上當前的key的過期時間一定小於當前時間戳(且不爲-1),所以TTL應該是小於0,而在代碼裏,作者將TTL<0的情況處理成TTL=0,那問題就在爲什麼第一個個if沒有生效上了,既該條件的主要判斷函數lookupKeyRead並沒有返回NULL,再查看該函數的代碼:

一次Redis <wbr>TTL <wbr>爲0的問題排查

從這開始終於看出點端倪了,該函數之所以沒有返回NULL,也是由於第一個if語句並沒有return NULL,從代碼的評論中可以看出,當redis作爲slave的時候,是可能不返回NULL的。

一次Redis <wbr>TTL <wbr>爲0的問題排查

從expireIfNeeded函數的註釋中可以看到,噹噹前的Redis爲Slave時,爲了保證主從數據的一致性,是並不會將當前key刪除的,觸發這一句:if (server.masterhost != NULL) return now > when;當前的時間now一定是大於key存儲的過期時間的,故該函數還是返回了1,這樣又回到lookupKeyRead,函數中。下面的這段函數起到決定性作用:

一次Redis <wbr>TTL <wbr>爲0的問題排查

以下幾個條件滿足的時候,該函數纔會Return NULL。

1.   當前鏈接存在

2.   當前鏈接不是master

3.   當前鏈接的命令存在

4.   當前鏈接的命令flags於REDIS_CMD_READONLY的與爲True

前三個比較在測試過程中,一定是爲True的,問題在第四個條件上,這裏又引出了Redis Command的flags,在客戶端,通過client list,可以查看到當前鏈接的flags:

一次Redis <wbr>TTL <wbr>爲0的問題排查

可以看到,執行ttl命令的flags爲N,而在下面的代碼中可以看出flags=N時,表示flags=0,所以在上面的代碼中,flags & REDIS_CMD_READONLY = 0 &2(REDIS_CMD_READONLY = 2,redis.h中定義),故這個if語句也沒有進入,所以並沒有返回NULL,因此導致ttlGenericCommand命令返回了TTL=0的結果。(至於redis使用這些flags的原理以及上面的if語句的原理,還需要更加深入的分析,這裏就不再闡述了)

 

所以,這種情況下,我們才知道,如果一個redis作爲slave,且將slave-read-only設置爲off,並寫入了一個帶有TTL的key時,當key過期後,該key是不會被Redis刪除的,且TTL在過期後永遠爲0。

帶着這樣的判斷,我們在該redis上執行info命令確認了一下,果然該redis是slave,諮詢了相關部署的同事得知,該業務在進行數據遷移過程中,存在多級複製和雙寫的情況,所以纔將redis slave設置爲可寫狀態,此時將slave的slaveof 設置成no one,既斷開同步,再次排查所有過期key的TTL都返回-2了。

所以,使用Redis的童鞋們,注意一下,在進行服務遷移等情況所構成多級複製鏈的時候,在relay上進行過期key的讀寫處理的時候需要注意TTL帶來的問題,若以後遇到TTL返回等於0的時候也可以第一時間確定問題所在了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章