Redis TTL 為0


地址: http://get.jobdeer.com/7297.get

一次Redis TTL 為0的問題排查

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

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

 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,如下圖所示:

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

 測試過程如上圖所示:

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

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

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

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

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

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

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

1.   當前鏈接存在

2.   當前鏈接不是master

3.   當前鏈接的命令存在

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

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

可 以看到,執行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的時候也可以第一時間確定問題所在了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM