【Redis5源碼學習】淺析redis命令之expire篇

Graperedis


命令語法

命令含義:爲給定 key 設置生存時間,當 key 過時時(生存時間爲 0 ),它會被自動刪除。
命令格式:數組

EXPIRE key seconds

命令實戰:緩存

redis> EXPIRE cache_page 30000   # 更新過時時間
(integer) 1

返回值:安全

設置成功返回 1 。
當 key 不存在或者不能爲 key 設置生存時間時(好比在低於 2.1.3 版本的 Redis 中你嘗試更新 key 的生存時間),返回 0 。服務器

源碼分析:

expire對應的函數是expireCommand:異步

/* EXPIRE key seconds */
void expireCommand(client *c) {
// 調用通用處理函數
    expireGenericCommand(c,mstime(),UNIT_SECONDS);
}

/*這是expire、pexpire、expireat和pexpireat的通用命令實現。由於commad第二個參數能夠是相對的,
也能夠是絕對的,因此「base time」參數用來表示基本時間是什麼(對於命令的at變量,或者相對過時的當前時間)。
單位是單位秒或者單位毫秒,僅用於argv[2]參數。基本時間老是以毫秒爲單位指定的。*/
void expireGenericCommand(client *c, long long basetime, int unit) {
    robj *key = c->argv[1], *param = c->argv[2];
    long long when; /*when被設置爲毫秒. */

    /* 取出param中的整數值或者嘗試將param中的數據儘量轉換成整數值存在when中,
    成功返回OK失敗則返回ERR*/
    if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
        return;

    /*若是傳入的過時時間是以秒爲單位的,那麼將它轉換爲毫秒*/
    if (unit == UNIT_SECONDS) when *= 1000;
    when += basetime;

    /* 查詢一下該鍵是否存在,不存在給客戶端返回信息 */
    if (lookupKeyWrite(c->db,key) == NULL) {
        addReply(c,shared.czero);
        return;
    }

     /*
      * 在載入AOF數據時,或者服務器爲附屬節點時,
      * 即便 EXPIRE 的 TTL 爲負數,或者 EXPIREAT 提供的時間戳已通過期,
      * 服務器也不會主動刪除這個鍵,而是等待主節點發來顯式的 DEL 命令。
     */
    if (when <= mstime() && !server.loading && !server.masterhost) {
       //進入這個函數的條件:when 提供的時間已通過期,未載入數據且服務器爲主節點(注意主服務器的masterhost==NULL)
        robj *aux;
        /*刪除該鍵,此處能夠看del命令的解析,在del命令解析中有分析redis同步和異步刪除的策略決定,此處再也不贅述*/
        int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
                                                    dbSyncDelete(c->db,key);
        serverAssertWithInfo(c,key,deleted);
        server.dirty++;

        /* Replicate/AOF this as an explicit DEL or UNLINK. */
        /* 傳播 DEL 或者unlink命令到AOF或者從服務器 */
        aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
        /*修改客戶端的參數數組*/
        rewriteClientCommandVector(c,2,aux,key);
        /* 發送鍵更改通知 */
        signalModifiedKey(c->db,key);
        notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
        addReply(c, shared.cone);
        return;
    } else {
        // 設置鍵的過時時間
        // 若是服務器爲附屬節點,或者服務器正在載入,根據上個if中的條件來推斷,至少when提供時間過時爲附屬節點就會設置
        // 這點猜想在redis中從屬節點不去主動作刪除操做,除非主節點同步del命令
        // 那麼這個 when 有可能已通過期的
        setExpire(c,c->db,key,when);
        addReply(c,shared.cone);
        signalModifiedKey(c->db,key);
        notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
 
        server.dirty++;
        return;
    }
}
//設置expire
void setExpire(client *c, redisDb *db, robj *key, long long when) {
    dictEntry *kde, *de;

    /* Reuse the sds from the main dict in the expire dict */
    /*首先從dict中查找,這個過程就是從db的dict根據key來查找的過程,首先會判斷是否有安全迭代器,若是沒有就會 進行rehash,防止哈        
     *希表混亂。而後進行hash,獲取索引值。經過索引咱們能夠遍歷dict的鏈表來拿到值。這裏會有dict上有hash值同樣的狀況,在這裏 
     *redis是用while循環來比較,知道key相同返回這一項。 
    */
    kde = dictFind(db->dict,key->ptr);
    serverAssertWithInfo(NULL,key,kde != NULL);
    /* dictAddRaw()的變通,只是多了一個若是有key則返回的一個過程,若是存在則返回爲null並返回當前項,反之將key進行hash索引        
     *index增長到dict中並返回dict。    
     */
    de = dictAddOrFind(db->expires,dictGetKey(kde));
    /* 設置鍵的過時時間
     * 這裏是直接使用整數值來保存過時時間,不是用 INT 編碼的 String 對象
    */
    dictSetSignedIntegerVal(de,when);

    int writable_slave = server.masterhost && server.repl_slave_ro == 0;
    if (c && writable_slave && !(c->flags & CLIENT_MASTER))
        rememberSlaveKeyWithExpire(db,key);
}

咱們gdb一個合法的例子來看一看他的流程:函數

  • 首先咱們來進入gdb的server和cli
    clipboard.png
    clipboard.png
  • 咱們能夠看到進入到getLongLongFromObjectOrReply後作的動做就是 取出param中的整數值或者嘗試將param中的數據儘量轉換成整數值存在when,打印when的值爲咱們設置的50000.
    clipboard.png
    clipboard.png
  • 判斷是否爲秒,是則轉化爲毫秒,接着加上basetime,結果爲,unix時間轉化爲2019-09-23 05:30:15,(大約13小時以後)符合預期,下一步
    clipboard.png
  • 接下來是查找是否有key,有則進行下一步
  • 由於咱們是正常進行過時設置,因此應該走的是else語句進行設置,設置就是從redisDb中找到咱們的key,而後更新expire時間,具體實現看上方代碼分析:
    clipboard.png
  • 接下來就是發信號通知等給客戶端。
    clipboard.png
  • 咱們繼續c執行命令,從客戶端收到執行成功
    clipboard.png

比較

redis此類命令有三種:EXPIREAT,PEXPIRE,PEXPIREAT
EXPIREAT 的做用和 EXPIRE 相似,都用於爲 key 設置生存時間。
不一樣在於 EXPIREAT 命令接受的時間參數是 UNIX 時間戳(unix timestamp)。
而PEXPIRE,PEXPIREAT和EXPIRE和EXPIREAT的區別在於PEXPIRE,PEXPIREAT是以毫秒爲單位,然後者用的是以秒爲單位。源碼分析

業務用處

在這裏我簡單的列舉幾點你們僅供參考:網站

  1. 限時的優惠活動信息
  2. 網站數據緩存(對於一些須要定時更新的數據,例如:積分排行榜)
  3. 手機驗證碼
  4. 限制網站訪客訪問頻率(例如:1分鐘最多訪問10次)
相關文章
相關標籤/搜索