redis source code analysis v1.0

Keywords: Redis network Netty Programming

Section I. references

First of all, the source code of redis is elegant and beautiful

Section 2. Structure

1, Reactor mode
The structure is as follows:

I understand that it's just a production consumption mode, but it doesn't need queues, and it's easier to understand the distribution mode. Or like observers, every view
The events that observers pay attention to are different. This mode is often used in NIO or Netty network programming. Register the event handler before use, and call back the handler when the event occurs,
After use, you can remove the handler
2, Persistence
If the specified number of write operations are performed within the specified time interval, the data in memory will be written to the disk. I.e. in the designated item
Record and generate a dump.rdb file. Redis will recover the data by loading the dump.rdb file. For example, there is a change in save 900 within 1900 seconds. During backup, a subprocess will be created independently,
Write the data to a temporary file (at this time, the data in memory is twice the original), and then replace the previous backup file with the temporary file.
APPEND ONLY MODE. Uses the form of log to record each write operation and append it to the file. When Redis restarts, it will execute the write instruction from the front to the back according to the contents of the log file
Complete data recovery. Principle of Rewriting: Redis will fork out a new process, read the data in memory and write it back to a temporary file. Old file was not read (j old file is too large). Last replace old
Aof file. Triggered when the size of the AOF file is twice that of the last rewrite and the file is larger than 64M

Section 3. Source code details

1, redis start
The entry is in the main() method in the server.c file

int main(int argc, char **argv) {
    struct timeval tv;
    int j;
	//Remove test code

	//Set process name
    /* We need to initialize our libraries, and the server configuration. */
    spt_init(argc, argv);
	//Set localization
    tzset(); /* Populates 'timezone' global. */
    //Memory overflow is to call the redisOutOfMemoryHandler method to print the log
    //Initializing random number generator with process pid and time
    //Get current time

    char hashseed[16];
    //Check the process parameters to see if the sentinel mode is specified
    server.sentinel_mode = checkForSentinelMode(argc,argv);
    /* Initialize various configuration items of server to the global variable struct redisServer server, which is defined in server.h and contains various configurations of redis */
    //Initialize various module library names

    /* Store the executable path and arguments in a safe place in order
     * to be able to restart the server later. */
    //Fill in the redis server full path, and restart later
    server.executable = getAbsolutePath(argv[0]);
    server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
    server.exec_argv[argc] = NULL;
    for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);

    /* We need to init sentinel right now as parsing the configuration file
     * in sentinel mode will have the effect of populating the sentinel
     * data structures with master nodes to monitor. */
    if (server.sentinel_mode) {
    	//Initialize sentinel mode configuration

    /* Check if we need to start in redis-check-rdb/aof mode. We just execute
     * the program main. However the program is part of the Redis executable
     * so that we can easily execute an RDB check on loading errors. */
    //Initialize the persistence scheme of redis, use rdb or aof, default rdb
    if (strstr(argv[0],"redis-check-rdb") != NULL)
    else if (strstr(argv[0],"redis-check-aof") != NULL)

    if (argc >= 2) {
        j = 1; /* First option to parse in argv[] */
        sds options = sdsempty();
        char *configfile = NULL;

        //Parsing help parameters
        /* Handle special options --help and --version */
        if (strcmp(argv[1], "-v") == 0 ||
            strcmp(argv[1], "--version") == 0) version();
        if (strcmp(argv[1], "--help") == 0 ||
            strcmp(argv[1], "-h") == 0) usage();
        if (strcmp(argv[1], "--test-memory") == 0) {

        /* First argument is the config file name? */
        //Resolve redis.conf file path
        if (argv[j][0] != '-' || argv[j][1] != '-') {
            configfile = argv[j];
            server.configfile = getAbsolutePath(configfile);
            /* Replace the config file in server.exec_argv with
             * its absolute path. */
            server.exec_argv[j] = zstrdup(server.configfile);

        /* All the other options are parsed and conceptually appended to the
         * configuration file. For instance --port 6380 will generate the
         * string "port 6380\n" to be parsed after the actual file name
         * is parsed, if any. */
        while(j != argc) {
            if (argv[j][0] == '-' && argv[j][1] == '-') {
                /* Option name */
                //Parse -- check RDB parameter
                if (!strcmp(argv[j], "--check-rdb")) {
                    /* Argument has no options, need to skip for parsing. */
                if (sdslen(options)) options = sdscat(options,"\n");
                options = sdscat(options,argv[j]+2);
                options = sdscat(options," ");
            } else {
                /* Option argument */
                options = sdscatrepr(options,argv[j],strlen(argv[j]));
                options = sdscat(options," ");
        if (server.sentinel_mode && configfile && *configfile == '-') {
                "Sentinel config from STDIN not allowed.");
                "Sentinel needs config file on disk to save state.  Exiting...");

    //Print the prompt when redis is started
    serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
        "Redis version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
            (sizeof(long) == 8) ? 64 : 32,
            strtol(redisGitDirty(),NULL,10) > 0,

    if (argc == 1) {
        serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
    } else {
        serverLog(LL_WARNING, "Configuration loaded");
    //Using supervise to monitor process restart, deadlock, etc
    server.supervised = redisIsSupervised(server.supervised_mode);
    int background = server.daemonize && !server.supervised;
    if (background) daemonize();

    /*Initialize the pid, current client list, master-slave list, etc. to create redis. Initialize the struct sharedobjectssstruct shared object
    Registration file, event processing class */
    if (background || server.pidfile)
    	//Create pid file
    //Set the process name according to whether redis is a sentry or a cluster mode
    //Print the welcome pattern of redis startup

    if (!server.sentinel_mode) {
        /* Things not needed when running in Sentinel mode. */
        serverLog(LL_WARNING,"Server initialized");
    #ifdef __linux__
    	//Dynamically load the previous so Library
        //Load data from disk file using AOF or RDB mode
        if (server.cluster_enabled) {
            if (verifyClusterConfigWithData() == C_ERR) {
                    "You can't have keys in a DB different than DB 0 when in "
                    "Cluster mode. Exiting.");
        if (server.ipfd_count > 0)
            serverLog(LL_NOTICE,"Ready to accept connections");
        if (server.sofd > 0)
            serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
    } else {

    /* Warning the user about suspicious maxmemory setting. */
    if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
        serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);

    /* Enable the ae event model of redis, receive client network requests, and support epoll, select, kqueue, and event ports based on Solaris*/
    return 0;

2, The IO model of redis

Enter aeMain() method in ae.c file, and the code is as follows:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    //Dead cycle treatment
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
        //The code of the actual io method is as follows
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);

To process the io model, the code is as follows. Put it here first, and then analyze it later

/* Process every pending time event, then every pending file event
 * (that may be registered by time event callbacks just processed).
 * Without special flags the function sleeps until some file event
 * fires, or when the next time event occurs (if any).
 * If flags is 0, the function does nothing and returns.
 * if flags has AE_ALL_EVENTS set, all the kind of events are processed.
 * if flags has AE_FILE_EVENTS set, file events are processed.
 * if flags has AE_TIME_EVENTS set, time events are processed.
 * if flags has AE_DONT_WAIT set the function returns ASAP until all
 * if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
 * the events that's possible to process without to wait are processed.
 * The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
    int processed = 0, numevents;

    /* Nothing to do? return ASAP */
    //No IO event, return directly
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;

    /* Note that we want call select() even if there are no
     * file events to process as long as we want to process time
     * events, in order to sleep until the next time event is ready
     * to fire. */
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        int j;
        aeTimeEvent *shortest = NULL;
        struct timeval tv, *tvp;

        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
            shortest = aeSearchNearestTimer(eventLoop);
        if (shortest) {
            long now_sec, now_ms;

            aeGetTime(&now_sec, &now_ms);
            tvp = &tv;

            /* How many milliseconds we need to wait for the next
             * time event to fire? */
            long long ms =
                (shortest->when_sec - now_sec)*1000 +
                shortest->when_ms - now_ms;

            if (ms > 0) {
                tvp->tv_sec = ms/1000;
                tvp->tv_usec = (ms % 1000)*1000;
            } else {
                tvp->tv_sec = 0;
                tvp->tv_usec = 0;
        } else {
            /* If we have to check for events but need to return
             * ASAP because of AE_DONT_WAIT we need to set the timeout
             * to zero */
            if (flags & AE_DONT_WAIT) {
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else {
                /* Otherwise we can block */
                tvp = NULL; /* wait forever */

        /* Call the multiplexing API, will return only on timeout or when
         * some event fires. */
        numevents = aeApiPoll(eventLoop, tvp);

        /* After sleep callback. */
        if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)

        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int fired = 0; /* Number of events fired for current fd. */

            /* Normally we execute the readable event first, and the writable
             * event laster. This is useful as sometimes we may be able
             * to serve the reply of a query immediately after processing the
             * query.
             * However if AE_BARRIER is set in the mask, our application is
             * asking us to do the reverse: never fire the writable event
             * after the readable. In such a case, we invert the calls.
             * This is useful when, for instance, we want to do things
             * in the beforeSleep() hook, like fsynching a file to disk,
             * before replying to a client. */
            int invert = fe->mask & AE_BARRIER;

            /* Note the "fe->mask & mask & ..." code: maybe an already
             * processed event removed an element that fired and we still
             * didn't processed, so we check if the event is still valid.
             * Fire the readable event if the call sequence is not
             * inverted. */
            if (!invert && fe->mask & mask & AE_READABLE) {

            /* Fire the writable event. */
            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {

            /* If we have to invert the call, fire the readable event now
             * after the writable one. */
            if (invert && fe->mask & mask & AE_READABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {

    /* Check time events */
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    return processed; /* return the number of processed file/time events */ 

3, Client request processing

1. File event and network event are also file events. The processing method is acceptTcpHandler() in the networking.c file
The acceptTcpHandler method is registered as a file event handling callback function in the initServer() method when redis is started. The code is as follows:

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) 
	int cport, cfd, max = MAX_ACCEPTS_PER_CALL;     
	char cip[NET_IP_STR_LEN];     

    while(max--) {
    	//Receive network connection
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        if (cfd == ANET_ERR) {
            if (errno != EWOULDBLOCK)
                    "Accepting client connection: %s", server.neterr);
        serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
        //Handle the connection request, and analyze the code in the next two places

2. The code for processing the request is as follows:
acceptCommonHandler() method in networking.c

static void acceptCommonHandler(int fd, int flags, char *ip) {
    client *c;
    /* Each connection request creates a client structure to process. In this method, register the readQueryFromClient() method to process the client request. The code will be analyzed in the next three places */
    if ((c = createClient(fd)) == NULL) {
            "Error registering fd event for the new client: %s (fd=%d)",
        close(fd); /* May be already closed, just ignore errors */
    /* If maxclient directive is set and this is one client more... close the
     * connection. Note that we create the client instead to check before
     * for this condition, since now the socket is already set in non-blocking
     * mode and we can send an error for free using the Kernel I/O */
    //Maximum number of client connections reached, error returned
    if (listLength(server.clients) > server.maxclients) {
        char *err = "-ERR max number of clients reached\r\n";

        /* That's a best effort error message, don't check write errors */
        if (write(c->fd,err,strlen(err)) == -1) {
            /* Nothing to do, Just to avoid the warning... */

    /* If the server is running in protected mode (the default) and there
     * is no password set, nor a specific interface is bound, we don't accept
     * requests from non loopback interfaces. Instead we try to explain the
     * user what to do to fix it if needed. */
    if (server.protected_mode &&
        server.bindaddr_count == 0 &&
        server.requirepass == NULL &&
        !(flags & CLIENT_UNIX_SOCKET) &&
        ip != NULL)

    c->flags |= flags;

3. Read customer request
Enter the readQueryFromClient() method in the networking.c file

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    client *c = (client*) privdata;
    int nread, readlen;
    size_t qblen;

    readlen = PROTO_IOBUF_LEN;
    /* If this is a multi bulk request, and we are processing a bulk reply
     * that is large enough, try to maximize the probability that the query
     * buffer contains exactly the SDS string representing the object, even
     * at the risk of requiring more read(2) calls. This way the function
     * processMultiBulkBuffer() can avoid copying buffers to create the
     * Redis Object representing the argument. */
    //Determine if you have requested data multiple times
    if (c->reqtype == PROTO_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
        && c->bulklen >= PROTO_MBULK_BIG_ARG)
        ssize_t remaining = (size_t)(c->bulklen+2)-sdslen(c->querybuf);

        /* Note that the 'remaining' variable may be zero in some edge case,
         * for example once we resume a blocked client after CLIENT PAUSE. */
        if (remaining > 0 && remaining < readlen) readlen = remaining;

    qblen = sdslen(c->querybuf);
    if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
    c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
    /* Read user data to Client.querybuf of type sds. sds is a string representation customized by redis. The content of the string is the requested string, such as get xxx*/
    nread = read(fd, c->querybuf+qblen, readlen);
    if (nread == -1) {
        if (errno == EAGAIN) {
        } else {
            serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno));
    } else if (nread == 0) {
        serverLog(LL_VERBOSE, "Client closed connection");
    } else if (c->flags & CLIENT_MASTER) {
        /* Append the query buffer to the pending (not applied) buffer
         * of the master. We'll use this buffer later in order to have a
         * copy of the string applied by the last command executed. */
        c->pending_querybuf = sdscatlen(c->pending_querybuf,

    c->lastinteraction = server.unixtime;
    if (c->flags & CLIENT_MASTER) c->read_reploff += nread;
    server.stat_net_input_bytes += nread;
    if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
        sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();

        bytes = sdscatrepr(bytes,c->querybuf,64);
        serverLog(LL_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);

    /* Time to process the buffer. If the client is a master we need to
     * compute the difference between the applied offset before and after
     * processing the buffer, to understand how much of the replication stream
     * was actually applied to the master state: this quantity, and its
     * corresponding part of the replication stream, will be propagated to
     * the sub-slaves and to the replication backlog. */
    /* To process the command requested by the user, call the processCommand() method in the file networking.c in turn,
    The code is analyzed in the next four places*/

4. Handle customer orders
Enter the processCommand() method in the server.c file, and use the command mode. The code is as follows:

/* If this function gets called we already read a whole
 * command, arguments are in the client argv/argc fields.
 * processCommand() execute the command or prepare the
 * server for a bulk read from the client.
 * If C_OK is returned the client is still alive and valid and
 * other operations can be performed by the caller. Otherwise
 * if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) {
    /* The QUIT command is handled separately. Normal command procs will
     * go through checking for replication and QUIT will cause trouble
     * when FORCE_REPLICATION is enabled and would be implemented in
     * a regular command proc. */
	//To process the quit command, the addReply() method is to issue a contract to the user
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        c->flags |= CLIENT_CLOSE_AFTER_REPLY;
        return C_ERR;

    /* Now lookup the command and check ASAP about trivial error conditions
     * such as wrong arity, bad command name and so forth. */
    /*Traverse the command list, which command to match. All commands are filled into the redisCommandTable global variable in the server.c file when redis is just started. Code 5 follows
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    if (!c->cmd) {
        sds args = sdsempty();
        int i;
        for (i=1; i < c->argc && sdslen(args) < 128; i++)
            args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
        addReplyErrorFormat(c,"unknown command `%s`, with args beginning with: %s",
            (char*)c->argv[0]->ptr, args);
        return C_OK;
    } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
               (c->argc < -c->cmd->arity)) {
        addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
        return C_OK;

    /* Check if the user is authenticated */
    //Check whether the customer has permission to execute the command
    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
        return C_OK;

    /* If cluster is enabled perform the cluster redirection here.
     * However we don't perform the redirection if:
     * 1) The sender of this command is our master.
     * 2) The command has no key arguments. */
    //Cluster processing
    if (server.cluster_enabled &&
        !(c->flags & CLIENT_MASTER) &&
        !(c->flags & CLIENT_LUA &&
          server.lua_caller->flags & CLIENT_MASTER) &&
        !(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&
          c->cmd->proc != execCommand))
        int hashslot;
        int error_code;
        clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,
        if (n == NULL || n != server.cluster->myself) {
            if (c->cmd->proc == execCommand) {
            } else {
            return C_OK;

    /* Handle the maxmemory directive.
     * First we try to free some memory if possible (if there are volatile
     * keys in the dataset). If there are not the only thing we can do
     * is returning an error.
     * Note that we do not want to reclaim memory if we are here re-entering
     * the event loop since there is a busy Lua script running in timeout
     * condition, to avoid mixing the propagation of scripts with the propagation
     * of DELs due to eviction. */
    //The server memory is not enough for processing, oom
    if (server.maxmemory && !server.lua_timedout) {
        int out_of_memory = freeMemoryIfNeeded() == C_ERR;
        /* freeMemoryIfNeeded may flush slave output buffers. This may result
         * into a slave, that may be the active client, to be freed. */
        if (server.current_client == NULL) return C_ERR;

        /* It was impossible to free enough memory, and the command the client
         * is trying to execute is denied during OOM conditions? Error. */
        if ((c->cmd->flags & CMD_DENYOOM) && out_of_memory) {
            addReply(c, shared.oomerr);
            return C_OK;

    /* Don't accept write commands if there are problems persisting on disk
     * and if this is a master instance. */
    //Write disk error
    int deny_write_type = writeCommandsDeniedByDiskError();
    if (deny_write_type != DISK_ERROR_TYPE_NONE &&
        server.masterhost == NULL &&
        (c->cmd->flags & CMD_WRITE ||
         c->cmd->proc == pingCommand))
        if (deny_write_type == DISK_ERROR_TYPE_RDB)
            addReply(c, shared.bgsaveerr);
                "-MISCONF Errors writing to the AOF file: %s\r\n",
        return C_OK;

    /* Don't accept write commands if there are not enough good slaves and
     * user configured the min-slaves-to-write option. */
    if (server.masterhost == NULL &&
        server.repl_min_slaves_to_write &&
        server.repl_min_slaves_max_lag &&
        c->cmd->flags & CMD_WRITE &&
        server.repl_good_slaves_count < server.repl_min_slaves_to_write)
        addReply(c, shared.noreplicaserr);
        return C_OK;


    /* Lua script too slow? Only allow a limited number of commands. */
    if (server.lua_timedout &&
          c->cmd->proc != authCommand &&
          c->cmd->proc != replconfCommand &&
        !(c->cmd->proc == shutdownCommand &&
          c->argc == 2 &&
          tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
        !(c->cmd->proc == scriptCommand &&
          c->argc == 2 &&
          tolower(((char*)c->argv[1]->ptr)[0]) == 'k'))
        addReply(c, shared.slowscripterr);
        return C_OK;

    /* Exec the command */
    if (c->flags & CLIENT_MULTI &&
        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
    } else {
    	//Execute the command, enter. If it is a get command, enter the getCommand() method to execute. The code is analyzed in the following 6 places
        c->woff = server.master_repl_offset;
        if (listLength(server.ready_keys))
    return C_OK;

5.redis command list processing method. For each command, find the processing function here

struct redisCommand redisCommandTable[] = {
    /*getCommand It is the processing function of get command. In the getCommand() method in the t_string.c file, the code is analyzed in the following 6 places*/

6.get command processing method
Enter the getCommand() method in the t_string.c file. The code is as follows:

void getCommand(client *c) {
	//The code is analyzed below

Get into

int getGenericCommand(client *c) {
    robj *o;

    //Query the value return of key, and analyze the code below
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return C_OK;

    if (o->type != OBJ_STRING) {
        return C_ERR;
    } else {
        return C_OK;

Enter the lookupKeyReadOrReply() method in the db.c file

robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
	//It calls lookupKeyReadWithFlags() method
    robj *o = lookupKeyRead(c->db, key);
    if (!o) addReply(c,reply);
    return o;

Enter the lookupKeyReadWithFlags() method in the db.c file

robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    robj *val;

    //Query whether the key expires
    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 safe
         * to return NULL ASAP. */
        if (server.masterhost == NULL) return NULL;

        /* However if we are in the context of a slave, expireIfNeeded() will
         * not really try to expire the key, it only returns information
         * about the "logical" status of the key: key expiring is up to the
         * master in order to have a consistent view of master's data set.
         * However, if the command caller is not the master, and as additional
         * safety measure, the command invoked is a read-only command, we can
         * safely return NULL here, and provide a more consistent behavior
         * to clients accessign expired values in a read-only fashion, that
         * will say the key as non existing.
         * Notably this covers GETs when slaves are used to scale reads. */
        if (server.current_client &&
            server.current_client != server.master &&
            server.current_client->cmd &&
            server.current_client->cmd->flags & CMD_READONLY)
            return NULL;
    //Query the value of key, and analyze the code below
    val = lookupKey(db,key,flags);
    if (val == NULL)
    return val;

Enter lookupKey() in db.c file to query key value

/* Low level key lookup API, not actually called directly from commands
 * implementations that should instead rely on lookupKeyRead(),
 * lookupKeyWrite() and lookupKeyReadWithFlags(). */
robj *lookupKey(redisDb *db, robj *key, int flags) {
	//Query the key from the dictionary, and analyze the code below
    dictEntry *de = dictFind(db->dict,key->ptr);
    if (de) {
        robj *val = dictGetVal(de);

        /* 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. */
        if (server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            !(flags & LOOKUP_NOTOUCH))
        	//Update access time of key
            if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
            } else {
                val->lru = LRU_CLOCK();
        return val;
    } else {
        return NULL;

According to the key, calculate the hash position, find the value value value in the array, specific dict dictionary storage format, in the following four. Dict dictionary analysis

dictEntry *dictFind(dict *d, const void *key)
    dictEntry *he;
    uint64_t h, idx, table;

    if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
    if (dictIsRehashing(d)) _dictRehashStep(d);
    //Calculate the hash value of key. Each type has its own hash function () method in the array
    h = dictHashKey(d, key);
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        //Take out the hash value
        he = d->ht[table].table[idx];
        while(he) {
        	//Traverse the linked list with the same hash location. If the key is the same, take the result and return
            if (key==he->key || dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        if (!dictIsRehashing(d)) return NULL;
    return NULL;

The default hashFunction() method is as follows:

static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
    unsigned int hash = 5381;

    while (len--)
        hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
    return hash;

4, dict dictionary

Used to store specific get and set data

typedef struct dict {
	//Dictionary type
    dictType *type;
    void *privdata;
    //Two dictionaries for storing time, the second for rehash
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
	//Address to store the array
    dictEntry **table;
    //Size of array
    unsigned long size;
    unsigned long sizemask;
    //Used size
    unsigned long used;
} dictht;
//Structure of array elements
typedef struct dictEntry {
	//key of elements
    void *key;
    //Value value of element
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    //The next key,value pair of the same hashcode
    struct dictEntry *next;
} dictEntry;
//Dictionary type
typedef struct dictType {
    uint64_t (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

dict cut two pictures from the Internet, which are clear as follows:

85 original articles published, praised 8, visited 70000+
Private letter follow

Posted by hungryOrb on Mon, 16 Mar 2020 23:54:32 -0700