Redis source code analysis and annotation: redis database and related command implementation (db)

Redis database and related command implementation

  1. Database management command
    The commands for database management are shown in the following table: redis keys command details
  2. The realization of database
    2.1 structure of database
typedef struct redisDb {
    // Key value pair dictionary, save all key value pairs in the database
    dict *dict;                 /* The keyspace for this DB */
    // Expiration dictionary, which stores the expiration time of the key and the key
    dict *expires;              /* Timeout of keys with a timeout set */
    // Save all the keys that block the client and the blocked client
    dict *blocking_keys;        /*Keys with clients waiting for data (BLPOP) */
    // The key in blocking state is saved, and the value is NULL
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    // Object module, used to save the key monitored by the wait command
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    // When the memory is insufficient, Redis will reclaim part of the space occupied by the key according to the LRU algorithm, and the eviction "pool" is a 16 array long, which stores the key that may be recycled
    // All the keys in the eviction pool are sorted from small to large according to idle idle time, and the key with the longest idle time is recycled each time
    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
    // Database ID
    int id;                     /* Database ID */
    // Average key expiration time
    long long avg_ttl;          /* Average TTL, just for stats */
} redisDb;

Blocking ﹣ keys and ready ﹣ keys are used for blocking commands of list type (BLPOP, etc.). For details, see: Redis list key command implementation
watched_keys is a module for things.
Eviction [pool] is used by Redis to reclaim memory in case of insufficient memory.
dict and expires and id are discussed in this paper.
Redis server and client also store database information, which can be intercepted as follows:

typedef struct client {
    redisDb *db;            /* Pointer to currently SELECTed DB. */
} client;

struct redisServer {
    redisDb *db;
    int dbnum;                      /* Total number of configured DBs */

When the Redis server initializes, it will create an array of redisDb types with a length of dbnum(16 by default). When the client logs in, the default database is database 0. When the SELECT index command is executed, the database will be switched. We use two clients, as shown below:

The SELECT index command is very simple. The source code is as follows:

// Switch database
int selectDb(client *c, int id) {
    // Illegal id, return error
    if (id < 0 || id >= server.dbnum)
        return C_ERR;
    // Set the database of the current client
    c->db = &server.db[id];
    return C_OK;

2.2 key value pair Dictionary of database
Redis is a key value database server, which stores all key value pairs in the dict dictionary member of redisDb structure (redis dictionary structure source code analysis).

The key value of the dictionary is the key of the database. Each key is a string object.

The value of key value to dictionary is the value of database. Each value can be any of string object, list object, hash table object, collection object and ordered collection object.

Analysis of Redis object system source code

The deletion of key objects in the database will also delete the value objects together with them. Therefore, in some operations, such as RENAME and other commands, the intermediate step will use the deletion of the original key. It is often necessary to add 1 to the reference count of the value object to protect the value object from being deleted. When the new key is set, the reference count of the value object will be reduced by 1.

We add several keys to a database, and show them in the figure:

Red represents key object, string object with RAW encoding, hash object. Simplify the structure and focus on reference counting.
Blue represents the value object, and the completion structure is as shown in the figure.

Each time a database finds a value object based on the key name, it can be retrieved by reading lookupKeyRead() or writing lookupKeyWrite(). There are certain differences between the two. The following shows the source code:

lookupKey() function
lookupKeyRead() or lookupKeyWrite() will call the underlying function. This function is very simple. First find the key object corresponding to the key name from the key value pair dictionary, and then take the value object out.

// This function is called by lookupKeyRead() and lookupKeyWrite() and lookupKeyReadWithFlags()
// Take the value object of the key from the database db, and return it if it exists, otherwise NULL
// Returns the value object of the key object
robj *lookupKey(redisDb *db, robj *key, int flags) {
    // Find the key object in the database and return the node address where the key is saved
    dictEntry *de = dictFind(db->dict,key->ptr);
    if (de) {   //If found
        robj *val = dictGetVal(de); //Take out the value object corresponding to the key

        /* Update the access time for the ageing algorithm.
         * Don't do it if we have a saving child, as this will trigger
         * a copy on write madness. */
        // Update key usage time
        if (server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            !(flags & LOOKUP_NOTOUCH))
            val->lru = LRU_CLOCK();
        return val; //Return value object
    } else {
        return NULL;

lookupKeyRead() function
The lookupKeyRead() function calls the lookupKeyReadWithFlags() function, which actually determines whether the current key is expired. If it is not, update the misses and hits information, and then return the value object.

There are also two macros:

define LOOKUP_NONE 0 //zero, no special significance
define LOOKUP_NOTOUCH (1<<0) //We don't want to change the key usage time if we just want to determine the key value object's encoding TYPE (TYPE command).
// Get the value object of key by read operation, and update the hit information
robj *lookupKeyRead(redisDb *db, robj *key) {
    return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);

// Get the value object of key by read operation, NULL returned if not found
// The side effects of calling this function are as follows:
// 1. If the arrival and expiration time of a key is TTL, the key is set as expired
// 2. The usage time information of the key is updated
// 3. Global key hits/misses status is updated
// Note: if the key has logically expired but still exists, the function returns NULL
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    robj *val;

    // If the key has expired and been deleted
    if (expireIfNeeded(db,key) == 1) {
        /* Key expired. If we are in the context of a master, expireIfNeeded()
         * returns 0 only when the key does not exist at all, so it's save
         * to return NULL ASAP. */
        // The key has expired. If it is the primary node environment, it means that the key has been absolutely deleted. If it is the slave node, it means that the key has been deleted,
        if (server.masterhost == NULL) return NULL;

        // If we are in the slave node environment, the expireifneed() function will not delete the expired key, it will only return the logical value of whether the key has been deleted
        // Expired keys are in the charge of the master node. In order to ensure the consistency of master and slave node data
        if (server.current_client &&
            server.current_client != server.master &&
            server.current_client->cmd &&
            server.current_client->cmd->flags & CMD_READONLY)
            return NULL;
    // If the key does not expire, the value object of the key is returned
    val = lookupKey(db,key,flags);
    // Update hit information
    if (val == NULL)
    return val;

lookupKeyWrite() function
The lookupKeyWrite() function first determines whether the key has expired, and then directly calls the lowest lookupKey() function. Compared with the lookupKeyRead() function, the process of updating misses and hits information is less.

// Take the value object of the key as a write operation, and do not update the hit information
robj *lookupKeyWrite(redisDb *db, robj *key) {
    return lookupKey(db,key,LOOKUP_NONE);

2.3 key expiration time
The expires dictionary in the redisBb structure holds the key that sets the expiration time and the expiration time. The client can set the expiration time for an existing key through the four commands of exit, PEXPIRE, exit and PEXPIREAT. When the expiration time of a key reaches, the key will no longer be available.

Let's use the figure to show the expired dictionary in the database, and use the key value just now to pair the objects in the dictionary.

Obviously, the key value takes up only one space for the same object in the dictionary and the expired dictionary, only increasing the reference count.
We focus on the deletion strategy of expired keys:

Lazy delete: when the client reads out the key with timeout attribute, if the expiration time set by the key has been exceeded, the delete will be executed and null will be returned.
Scheduled deletion: Redis maintains a scheduled task internally, which runs 10 times per second by default.
We give the code for lazy deletion. This function, expireifneed(), is called by all Redis commands that read and write the database before execution to delete the expired key.

// Check if the key is out of date. If it is out of date, remove it from the database
// Return 0 means no expiration or no expiration time, return 1 means the key is deleted
int expireIfNeeded(redisDb *db, robj *key) {
    //Get expiration time in milliseconds
    mstime_t when = getExpire(db,key);
    mstime_t now;

    // No expiration time, return directly
    if (when < 0) return 0; /* No expire for this key */

    /* Don't expire anything while loading. It will be done later. */
    // If the server is loading, the expiration check will not be performed
    if (server.loading) return 0;

    /* If we are in the context of a Lua script, we claim that time is
     * blocked to when the Lua script started. This way a key can expire
     * only the first time it is accessed and not in the middle of the
     * script execution, making propagation to slaves / AOF consistent.
     * See issue #1525 on Github for more information. */
    // Returns a Unix time in milliseconds
    now = server.lua_caller ? server.lua_time_start : mstime();

    /* If we are running in the context of a slave, return ASAP:
     * the slave key expiration is controlled by the master that will
     * send us synthesized DEL operations for expired keys.
     * Still we try to return the right information to the caller,
     * that is, 0 if we think the key should be still valid, 1 if
     * we think the key is expired at this time. */
    // If the server is replicating the master and slave nodes, the expired key of the slave node should be deleted by the synchronous deletion sent by the master node, but not by itself
    // Only the correct logical information is returned from the node. 0 indicates that the key is still not expired, and 1 indicates that the key is expired.
    if (server.masterhost != NULL) return now > when;

    /* Return when this key has not expired */
    // When the key has not expired, directly return 0
    if (now <= when) return 0;

    /* Delete the key */
    // Key expired, delete key
    server.stat_expiredkeys++;              //Number of expired keys plus 1
    propagateExpire(db,key);                //Propagate expired key to AOF file and slave node
    notifyKeyspaceEvent(NOTIFY_EXPIRED,     //Send "expired" event notification
    return dbDelete(db,key);                //Remove key from database
  1. Implementation of database related commands
    We only list some command implementations. All code annotations can be viewed in github: Redis database implementation (db.c)

3.1 key space command
The underlying implementation of commands like SCAN

// SCAN cursor [MATCH pattern] [COUNT count]
// Bottom level implementation of commands such as SCAN, HSCAN, SSCAN and ZSCAN
// o object must be a hash or collection object, otherwise the command will operate on the current database
// If o is not NULL, it means that it is a hash or collection object. The function will skip these key objects and analyze the parameters
// If it is a hash object, it returns a key value pair
void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
    int i, j;
    list *keys = listCreate();  //Create a list
    listNode *node, *nextnode;
    long count = 10;
    sds pat = NULL;
    int patlen = 0, use_pattern = 0;
    dict *ht;

    /* Object must be NULL (to iterate keys names), or the type of the object
     * must be Set, Sorted Set, or Hash. */
    // Check of input type, either iterate key name, current collection object, hash object or ordered collection object
    serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH ||
                o->type == OBJ_ZSET);

    /* Set i to the first option argument. The previous one is the cursor. */
    // Calculate the subscript of the first parameter. If it is a key name, skip the key
    i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */

    /* Step 1: Parse options. */
    // 1. Resolution options
    while (i < c->argc) {
        j = c->argc - i;
        // Set the COUNT parameter. The function of the COUNT option is to let the user tell the iteration command how many elements should be returned in each iteration.
        if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
            //Save the number to count
            if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
                != C_OK)
                goto cleanup;

            // Syntax error if number is less than 1
            if (count < 1) {
                goto cleanup;

            i += 2; //Parameter skips two parsed
        // Set the MATCH parameter so that the command returns only elements that MATCH the given pattern.
        } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
            pat = c->argv[i+1]->ptr;    //pattern string
            patlen = sdslen(pat);       //pattern string length

            /* The pattern always matches if it is exactly "*", so it is
             * equivalent to disabling it. */
            // If the pattern is "*", no matching is needed, all are returned, set to 0
            use_pattern = !(pat[0] == '*' && patlen == 1);

            i += 2;
        } else {
            goto cleanup;

    /* Step 2: Iterate the collection.
     * Note that if the object is encoded with a ziplist, intset, or any other
     * representation that is not a hash table, we are sure that it is also
     * composed of a small number of elements. So to avoid taking state we
     * just return everything inside the object in a single call, setting the
     * cursor to zero to signal the end of the iteration. */

    /* Handle the case of a hash table. */
    // 2. If the object is a ziplist, intset, or other rather than a hash table, these types contain only a small number of elements
    // We return all its elements to the caller at one time, and set the cursor cursor to 0 to mark the completion of iteration
    ht = NULL;
    // Iteration target is database
    if (o == NULL) {
        ht = c->db->dict;
    // Iterative target is set object of HT coding
    } else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) {
        ht = o->ptr;
    // The iteration target is the HT encoded hash object
    } else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) {
        ht = o->ptr;
        count *= 2; /* We return key / value for this type. */
    // The goal of iteration is an ordered set object encoded by skiplist
    } else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) {
        zset *zs = o->ptr;
        ht = zs->dict;
        count *= 2; /* We return key / value for this type. */

    if (ht) {
        void *privdata[2];
        /* We set the max number of iterations to ten times the specified
         * COUNT, so if the hash table is in a pathological state (very
         * sparsely populated) we avoid to block too much time at the cost
         * of returning no or very few elements. */
        // Set the maximum iteration length to 10*count times
        long maxiterations = count*10;

        /* We pass two pointers to the callback: the list to which it will
         * add new elements, and the object containing the dictionary so that
         * it is possible to fetch more data in a type-dependent way. */
        // The parameter privdata of the callback function scanCallback is an array, which stores the keys and values of the iterated object
        // Another parameter of the callback function scanCallback is a dictionary object
        // The function of the callback function scanCallback is to extract key value pairs from the dictionary object, regardless of the data type of the dictionary object
        privdata[0] = keys;
        privdata[1] = o;
        // Cycle scan ht, start with cursor, call the specified scanCallback function, and put forward the data in ht to the list keys just created
        do {
            cursor = dictScan(ht, cursor, scanCallback, privdata);
        } while (cursor &&
              maxiterations-- &&
              listLength(keys) < (unsigned long)count);//If you don't finish iterating, or if you don't have enough count, continue cycling

    // If it is a set object but the encoding is not HT, it is an integer set
    } else if (o->type == OBJ_SET) {
        int pos = 0;
        int64_t ll;
        // Take out the integer value, build a string object and add it to the keys list. The cursor is set to 0, indicating that the iteration is completed
        cursor = 0;
    // If it is a hash object or an ordered collection object, but the encoding is not HT, it is ziplist
    } else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) {
        unsigned char *p = ziplistIndex(o->ptr,0);
        unsigned char *vstr;
        unsigned int vlen;
        long long vll;

        while(p) {
            // Take out the value, build the same string object according to different types of values, and add it to the keys list
                (vstr != NULL) ? createStringObject((char*)vstr,vlen) :
            p = ziplistNext(o->ptr,p);
        cursor = 0;
    } else {
        serverPanic("Not handled encoding in SCAN.");

    /* Step 3: Filter elements. */
    // 3. Filter if MATCH parameter is set
    node = listFirst(keys); //Address of the first node of the linked list
    while (node) {
        robj *kobj = listNodeValue(node);   //key object
        nextnode = listNextNode(node);      //Next node address
        int filter = 0; //No filtering by default

        /* Filter element if it does not match the pattern. */
        //pattern is not "*" so filter
        if (!filter && use_pattern) {
            // If kobj is a string object
            if (sdsEncodedObject(kobj)) {
                // kobj value does not match pattern, set filter flag
                if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
                    filter = 1;
            // If kobj is an integer object
            } else {
                char buf[LONG_STR_SIZE];
                int len;

                serverAssert(kobj->encoding == OBJ_ENCODING_INT);
                // Convert integer to string type, save in buf
                len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
                //buf value does not match pattern, set filter flag
                if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;

        /* Filter element if it is an expired key. */
        // The iteration target is the database, if kobj is the expired key, filter
        if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;

        /* Remove the element and its associted value if needed. */
        // If the key meets the above filter conditions, delete it from the keys list and release it
        if (filter) {
            listDelNode(keys, node);

        /* If this is a hash or a sorted set, we have a flat list of
         * key-value elements, so if this element was filtered, remove the
         * value, or skip it if it was not filtered: we only match keys. */
        // If the current iteration target is an ordered set or hash object, the keys list holds key value pairs. If the key object is filtered, the value object should also be filtered
        if (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) {
            node = nextnode;
            nextnode = listNextNode(node);  //Node address of value object
            // If the key meets the above filter conditions, delete it from the keys list and release it
            if (filter) {
                kobj = listNodeValue(node); //Fetch value object
                listDelNode(keys, node);    //delete
        node = nextnode;

    /* Step 4: Reply to the client. */
    // 4. Reply to the client
    addReplyMultiBulkLen(c, 2);     //Part 2: a cursor and a list
    addReplyBulkLongLong(c,cursor); //Reply cursor

    addReplyMultiBulkLen(c, listLength(keys));  //Reply list length

    //Loop through the elements in the list and release
    while ((node = listFirst(keys)) != NULL) {
        robj *kobj = listNodeValue(node);
        addReplyBulk(c, kobj);
        listDelNode(keys, node);

// cleanup code
    listSetFreeMethod(keys,decrRefCountVoid);   //How to set a specific release list decrefcountvoid
    listRelease(keys);                          //release

The underlying implementation of RENAME and RENAME commands

// RENAME key newkey
// RENAMENX key newkey
// The underlying implementation of RENAME and RENAME commands
void renameGenericCommand(client *c, int nx) {
    robj *o;
    long long expire;
    int samekey = 0;

    /* When source and dest key is the same, no operation is performed,
     * if the key exists, however we still return an error on unexisting key. */
    // If the key is the same as the newkey, set the samekey flag
    if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1;

    // Read the value object of key with write operation
    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)

    // If the key and newkey are the same, nx sends 0 for 1, otherwise ok
    if (samekey) {
        addReply(c,nx ? shared.czero : shared.ok);

    // Increase the reference count of the value object, protect it, and use it to associate with the newkey to prevent the value object from being deleted along with the key
    // The expiration time of the backup key, which will be used as the expiration time of the newkey in the future
    expire = getExpire(c->db,c->argv[1]);
    // Determine whether the value object of newkey exists
    if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
        // If the nx flag is set, the existing conditions will not be met. Send 0
        if (nx) {
        /* Overwrite: delete the old key before creating the new one
         * with the same name. */
        dbDelete(c->db,c->argv[2]); //Delete old newkey object
    // Associate newkey with value object of key
    // If the expiration time is set for newkey, set the expiration time for newkey
    if (expire != -1) setExpire(c->db,c->argv[2],expire);
    // Delete key
    // Send the signal that these two keys are modified
    // Send event notifications for different commands
    server.dirty++;     //Update dirty key
    addReply(c,nx ? shared.cone : shared.ok);

MOVE command

// MOVE key db moves the key of the current database to the given database db.
// MOVE command implementation
void moveCommand(client *c) {
    robj *o;
    redisDb *src, *dst;
    int srcid;
    long long dbid, expire;

    // The server is in cluster mode and does not support multiple databases
    if (server.cluster_enabled) {
        addReplyError(c,"MOVE is not allowed in cluster mode");

    /* Obtain source and target DB pointers */
    // Get the id of the source database and the source database
    src = c->db;
    srcid = c->db->id;

    // Save the value of parameter db to dbid and switch to the database
    if (getLongLongFromObject(c->argv[2],&dbid) == C_ERR ||
        dbid < INT_MIN || dbid > INT_MAX ||
        selectDb(c,dbid) == C_ERR)
    // Target database
    dst = c->db;
    // Switch back to source database
    selectDb(c,srcid); /* Back to the source DB */

    /* If the user is moving using as target the same
     * DB as the source DB it is probably an error. */
    // If the database switched before and after is the same, the relevant error will be returned
    if (src == dst) {

    /* Check if the element exists and get a reference */
    // Fetching objects from the source database by write operation
    o = lookupKeyWrite(c->db,c->argv[1]);
    if (!o) {
        addReply(c,shared.czero);   //Send 0 does not exist
    // Expiration time of backup key
    expire = getExpire(c->db,c->argv[1]);

    /* Return zero if the key already exists in the target DB */
    // Determine whether the current key exists in the target database. If it exists, return it directly. Send 0
    if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
    // Add the key value object to the target database
    // Set the expiration time of the key after the move
    if (expire != -1) setExpire(dst,c->argv[1],expire);
    incrRefCount(o);    //Increase reference count

    /* OK! key moved, free the entry in the source DB */
    // Remove the key and associated value objects from the source database
    server.dirty++; //Update dirty key
    addReply(c,shared.cone);    //Reply 1

3.2 expired orders
The underlying implementation of the command "exit, pexpire, exit, pexpireat"

// EXPIRE key seconds
// EXPIREAT key timestamp
// PEXPIRE key milliseconds
// PEXPIREAT key milliseconds-timestamp
// The underlying implementation of the command "exit, pexpire, exit, pexpireat"
// The basetime parameter may be absolute or relative. Basetime is 0 when executing AT command, otherwise the current absolute time is saved
// Unit is unit ﹣ seconds or unit ﹣ milliseconds, but the base time is always in milliseconds.
void expireGenericCommand(client *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. */

    // Take out the time parameter and save it to when
    if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)

    // If the expiration time is in seconds, convert to milliseconds
    if (unit == UNIT_SECONDS) when *= 1000;
    // Absolute time
    when += basetime;

    /* No key, return zero. */
    // Judge whether the key is in the database and return 0 if it is not
    if (lookupKeyWrite(c->db,key) == NULL) {

    /* 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.
     * 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. */
    // If AOF data is currently being loaded or in the slave environment, even if the TTL of express is negative, or the timestamp of express has expired
    // The server will not execute the DEL command, and sets the expiration TTL as the expiration time of the key, waiting for the DEL command from the master node

    // If when is obsolete, the server is the primary node and AOF data is not loaded
    if (when <= mstime() && !server.loading && !server.masterhost) {
        robj *aux;

        // Remove key from database
        server.dirty++; //Update dirty key

        /* Replicate/AOF this as an explicit DEL. */
        // Create a DEL command
        aux = createStringObject("DEL",3);
        rewriteClientCommandVector(c,2,aux,key);    //Modify the parameter list of the client to DEL command
        // Send signal with key modified
        // Send event notification for "del"
        addReply(c, shared.cone);

    // If the current server is a slave or the server is loading AOF data
    // Set to expiration time regardless of when
    } else {
        // Set expiration time
        signalModifiedKey(c->db,key);   //Send signal with key modified
        notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id); //Send event notification for "expire"
        server.dirty++; //Update dirty key

Bottom implementation of TTL and PTTL commands

// TTL key
// PTTL key
// The underlying implementation of TTL and PTTL commands, output ﹣ Ms = 1, return MS, return s = 0
void ttlGenericCommand(client *c, int output_ms) {
    long long expire, ttl = -1;

    /* If the key does not exist at all, return -2 */
    // Determine whether the key exists in the database, and do not modify the use time of the key
    if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) {
    /* The key exists. Return -1 if it has no expire, or the actual
     * TTL value otherwise. */
    // If the key exists, the expiration time of the current key is backed up
    expire = getExpire(c->db,c->argv[1]);

    // If expiration time is set
    if (expire != -1) {
        ttl = expire-mstime();  //Calculate lifetime
        if (ttl < 0) ttl = 0;
    // If the key is permanent
    if (ttl == -1) {
        addReplyLongLong(c,-1); //Send -1
    } else {
        addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); //Send lifetime
Published 52 original articles, won praise 9, visited 30000+
Private letter follow

Tags: Database Redis encoding less

Posted on Tue, 14 Jan 2020 00:02:46 -0800 by vinod79