redis學習筆記——expire、pexpire、expireat、pexpireat的執行過程

這裏主要講的Redis是怎麼樣設置過時鍵的,能夠算做後續"Redis過時鍵的刪除策略"的前篇或者說預備知識。java

在瞭解過時鍵問題前咱們首先須要對redis的數據庫和數據庫鍵空間有必定的瞭解:redis

struct redisServer {
    // ...
    // 一個數組,保存着服務器中的全部數據庫
    redisDb *db;數據庫

  //服務器的數據庫數量,dbnum屬性的值由服務器配置的database選項決定,默認狀況下,該選項的值爲16
    int dbnum;數組

    // ...
};服務器

在服務器內部,客戶端狀態redisClient結構的db屬性記錄了客戶端當前的目標數據庫,這個屬性是一個指向redisDb結構的指針:函數

typedef struct redisClient {
// ...
// 記錄客戶端當前正在使用的數據庫
redisDb *db;
// ...
} redisClient;學習

 

如今咱們再來看看redisDb 結構,redisDb結構的dict字典保存了數據庫中的全部鍵值對,咱們將這個字典稱爲鍵空間(key space):this

typedef struct redisDb {
    // ...
    // 
數據庫鍵空間,保存着數據庫中的全部鍵值對
    dict *dict;
    // ...
} redisDb;spa

  • 鍵空間的鍵也就是數據庫的鍵,每一個鍵都是一個字符串對象;
  • 鍵空間的值也就是數據庫的值,每一個值能夠是字符串對象、列表對象、哈希表對象、集合對象和有序集合對象中的任意一種Redis對象。

下面是一個例子:翻譯

根據這個鍵空間,執行相關的添加、刪除、更新等操做的即可以比較容易理解,咱們此處也忽略不講了。

下面咱們再來介紹一下redisDb結構中的另一個字典expires,這個字典保存了數據庫中全部鍵的過時時間,咱們稱這個字典爲過時字典(注意這裏面只保存着鍵的過時時間,可不是說這個字典裏面的鍵都是過時的):

  • 過時字典的鍵是一個指針,這個指針指向鍵空間中的某個鍵對象(也便是某個數據庫鍵);
  • 過時字典的值是一個long long類型的整數,這個整數保存了鍵所指向的數據庫鍵的過時時間——一個毫秒精度的UNIX時間戳;

 

有了上面的知識咱們下載即可以來看看四個命令:expire、pexpire、expireat、pexpireat的實現過程。

四個命令的使用是比較簡單的:EXPIRE <key> <seconds> 如:EXPIRE book 100

              PEXPIRE <key> <millionseconds>

              EXPIREAT <key> <timestamp> 

              PEXPIREAT <key> <timestamp> 如:PEXPIREAT book 1388556000000(2014年1月1日零時)其實我也不知道這是怎麼算出來的!!!

注意:利用PERSIST命令能夠移除一個鍵的過時時間。

如:redis> PEXPIREAT message 1391234400000
(integer) 1

其餘的命令相似。不過值得一提的是:EXPIRE、EXPIREAT、PEXPIRE所有是轉換成PEXPIREAT來實現的。下面來看看每一個命令的實現函數:

void expireCommand(redisClient *c) {
expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
void expireatCommand(redisClient *c) {
expireGenericCommand(c,0,UNIT_SECONDS);
}
void pexpireCommand(redisClient *c) {
expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
}
void pexpireatCommand(redisClient *c) {
expireGenericCommand(c,0,UNIT_MILLISECONDS);
}

他們都調用了expireGenericCommand()函數進行實現,那咱們如今就來分析一下expireGenericCommand函數是怎麼實現的:

 

/*-----------------------------------------------------------------------------
 * Expires Commands
 *----------------------------------------------------------------------------*/

/* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT
 * and PEXPIREAT. Because the commad second argument may be relative or absolute
 * the "basetime" argument is used to signal what the base time is (either 0
 * for *AT variants of the command, or the current time for relative expires).
 *
 * 這個函數是 EXPIRE 、 PEXPIRE 、 EXPIREAT 和 PEXPIREAT 命令的底層實現函數。
 * 命令的第二個參數多是絕對值,也多是相對值。
 * 當執行 *AT 命令時, basetime 爲 0 ,在其餘狀況下,它保存的就是當前的絕對時間。
 *
 * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
 * the argv[2] parameter. The basetime is always specified in milliseconds. 
 *
 * unit 用於指定 argv[2] (傳入過時時間)的格式,
 * 它能夠是 UNIT_SECONDS 或 UNIT_MILLISECONDS ,
 * basetime 參數則老是毫秒格式的。
 */
void expireGenericCommand(redisClient *c, long long basetime, int unit) {
    robj *key = c->argv[1], *param = c->argv[2];
    long long when; /* unix time in milliseconds when the key will expire. */
    // 取出param中的整數值或者嘗試將param中的數據儘量轉換成整數值存在when中,成功返回REDIS_OK失敗則返回REDIS_ERR
    if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
        return;
    // 若是傳入的過時時間是以秒爲單位的,那麼將它轉換爲毫秒
    if (unit == UNIT_SECONDS) when *= 1000;
    when += basetime;

    /* No key, return zero. */
    // 查詢一下該鍵是否存在
    if (lookupKeyRead(c->db,key) == NULL) {
        addReply(c,shared.czero);
        return;
    }
    /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
     * should never be executed as a DEL when load the AOF or in the context
     * of a slave instance.
     *
     * 在載入AOF數據時,或者服務器爲附屬節點時,
     * 即便 EXPIRE 的 TTL 爲負數,或者 EXPIREAT 提供的時間戳已通過期,
     * 服務器也不會主動刪除這個鍵,而是等待主節點發來顯式的 DEL 命令。
     *
     * Instead we take the other branch of the IF statement setting an expire
     * (possibly in the past) and wait for an explicit DEL from the master. 
     *
     * 程序會繼續將(一個可能已通過期的 TTL)設置爲鍵的過時時間,
     * 而且等待主節點發來 DEL 命令。
     */
    if (when <= mstime() && !server.loading && !server.masterhost) {
        // when 提供的時間已通過期,服務器爲主節點(注意主服務器的masterhost==NULL),而且沒在載入數據
        robj *aux;
        //刪除該鍵
        redisAssertWithInfo(c,key,dbDelete(c->db,key));
        server.dirty++;

        /* Replicate/AOF this as an explicit DEL. */
        // 傳播 DEL 命令到AOF或者從服務器
        aux = createStringObject("DEL",3);
        //修改客戶端的參數數組
        rewriteClientCommandVector(c,2,aux,key);
        decrRefCount(aux);

        //信號:鍵值已經改變了。調用touchWatchedKey(db,key)
        signalModifiedKey(c->db,key);
        //發送鍵空間通知和鍵事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);

        addReply(c, shared.cone);

        return;
    } else {
        // 設置鍵的過時時間
        // 若是服務器爲附屬節點,或者服務器正在載入,
        // 那麼這個 when 有可能已通過期的
        setExpire(c->db,key,when);

        addReply(c,shared.cone);

        signalModifiedKey(c->db,key);
        notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id);
        server.dirty++;
        return;
    }
}

下面我將對本身在看代碼時裏面不太熟悉的幾個函數進行說明:

  • getLongLongFromObjectOrReply(redisClient *c,robj *o,long long *target,const char *msg)

函數目的:嘗試從對象o中取出整數值,或者嘗試將對象o中的值換成整數值,並將獲得的值保存在target中。同時若是轉換取出/轉成功的話,返回REDIS_OK,不然返回REDIS_ERR,並向客戶端發送一條出錯回覆。

大體實現過程: getLongLongFromObjectOrReply——>getLongLongFromObject——>stroll()最後主要看看stoll函數的實現過程就OK了。

  • lookupKeyRead(redisDb *db,robj *key)

函數目的:爲執行讀取操做而取出鍵key在數據庫中的值。並根據是否成功找到值,更新服務器中的命中和不命中信息。找搞則返回值,沒找到則返回NULL

函數實現

  • rewriteClientCommandVector(redisClient *c,int argc,...)

函數目的:修改客戶端的參數數組。這其中涉及到C語言可變參數的運用,能夠自行學習。

函數實現:若是理解了C語言的可變參數的大體運用的話,函數的實現過程已經大體理解了。其中須要注意的是lookupCommandOrOriginal()函數,lookupCommandOrOriginal()目的是在命令被改名以後,將改名後正確的redisCommand進行返回。

  • signalModifiedKey(c->db,key)鍵值已經改變了的信號。調用touchWatchedKey(db,key)(「觸碰」一個鍵,若是這個鍵在某個客戶端watch下,那這個客戶端執行的EXEC時事務將失敗)
  • notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id) 發送鍵空間通知和鍵事件通知(該部分知識從此會提到)

上面問題補:

redis> PEXPIREAT message 1388556000000(2014年1月1日零時)
(integer) 1

猜想:1388556000000應該是表示1997年1月1日零時——2014年1月1日零時的毫秒數,後面經過查詢Java知識小小的寫了一個程序驗證本身的猜想。

下面是Java程序:

import java.util.Date;
import java.text.*;
public class demo1 {

    public static void main(String[] args) throws ParseException {
        // TODO Auto-generated method stub
        SimpleDateFormat dateFormat=new SimpleDateFormat("MM-dd-yyyy");
        String txtDate="1-01-2014";
        Date date=dateFormat.parse(txtDate);
        //System.out.println(date);
        System.out.println(date.getTime());
    }
}
結果:1388505600000

最終結果和本身預期的相差了50400000秒,簡單計算一下也就是14小時。好了推測一下,由於數據1388556000000是借用了《Redis設計與實現》書上的實例,而這本書是翻譯過來的,那麼不難推測1388556000000應該表示的是美國時間2014年1月1日零時。因此猜想是對的。

相關文章
相關標籤/搜索