Redis is a great example of high-performance server. When I worked as a backend developer, my mentor recommended that I should read the code of Redis. And so I did, it’s rewarding. As the time goes by, the ideas behind Redis are still cutting-edge, so I think it’s time to retrospect the implementation of Redis. This post is a good beginning.

It’s my habit to start from the entry when meeting a big project. In Redis, server.c is responsible for bootstrap, that’s what I pay close attention to in this post.

The code analyzed here is based on 5.0.0

A global variable named server with type redisServer holds the state for Redis server.

struct redisServer server;

The fields of redisServer can be found in server.h, and we only focus on some ones that are used when starting.

struct redisServer {
    char *configfile;           /* Absolute config file path, or NULL */
    char *executable;           /* Absolute executable file path. */
    char **exec_argv;           /* Executable argv vector (copy). */

    redisDb *db;
    dict *commands;             /* Command table */
    dict *orig_commands;        /* Command table before command renaming. */

    int port;                   /* TCP listening port */

    char runid[CONFIG_RUN_ID_SIZE+1];  /* ID always different at every exec. */

    char *masterhost;               /* Hostname of master */
    int masterport;                 /* Port of master */

    long long slowlog_entry_id;     /* SLOWLOG current entry ID */
    long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
    unsigned long slowlog_max_len;     /* SLOWLOG max number of items logged */

    list *clients;              /* List of active clients */
    list *slaves, *monitors;    /* List of slaves and MONITORs */

    aeEventLoop *el;
}

The whole process of launching is implemented in main() method, and the first step is initializing redisServer, which is done by calling initServerConfig() method.

Init redisServer struct

Let’s see what initServerConfig() does.

void initServerConfig(void) {

    // mutex
    pthread_mutex_init(&server.next_client_id_mutex,NULL);
    pthread_mutex_init(&server.lruclock_mutex,NULL);
    pthread_mutex_init(&server.unixtime_mutex,NULL);


    // run id
    getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
    server.runid[CONFIG_RUN_ID_SIZE] = '\0';

    // default port, 9379
    server.port = CONFIG_DEFAULT_SERVER_PORT;    

    // LRU clock
    unsigned int lruclock = getLRUClock();
    atomicSet(server.lruclock,lruclock);
    resetServerSaveParams();

    // replication
    server.masterhost = NULL;
    server.masterport = 6379;
    server.repl_state = REPL_STATE_NONE;
    server_slave_priority = CONFIG_DEFAULT_SLAVE_PRIORITY;


    // command table
    server.commands = dictCreate(&commandTableDictType,NULL);
    server.orig_commands = dictCreate(&commandTableDictType,NULL);
    populateCommandTable();

    // slow log
    server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
    server.slowlog_max_len = CONFIG_DEFAULT_SLOWLOG_MAX_LEN;
}

After initServerConfig(), Redis will store the executable path and arguments for restarting the server 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]);

If Sentinel model is enabled, the following operations will be performed.

if (server.sentinel_mode) {
    initSentinelConfig();
    initSentinel();
}

The reason to init sentinel here is parsing the configuration file in Sentinel model will have the effect of populating the sentinel data structures with master nodes to monitor.

Parsing configuration file

Then, Redis parses and loads the server configuration from specified filename by loadServerConfig(char *filename, char *options).

I simplify the original code for easy understanding.

void loadServerConfig(char *filename, char *options) {
    sds config = sdsempty();
    char buf[CONFIG_MAX_LINE+1];

    while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
            config = sdscat(config,buf);

   loadServerConfigFromString(config);
    sdsfree(config);    
}

A new data structure named sds appears first, and we can deduce that it is something related to String from the statement loadServerConfigFromString(config);. Yes, it is short for “Simple Dynamic String”, and a representation for String in Redis.

loadServerConfigFromString method is full of string manipulations, such as,

  • Skip comments and black lines
  • Split into arguments
  • Skip the line if the resulting command vector is empty

Init the state of server

Up to now, many variables in redisServer struct is still dangling and need to be assigned dynamically. initServer() is responsible for doing this.

void initServer(void) {
    // process id
    server.pid = getpid();

    // clients list
    server.clients = listCreate();

    // slaves list
    server.slaves = listCreate();

    // shared objects for common values
    createSharedObjects();

    // event loop
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);

    // open TCP listening socket for user
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);

    // create and init databases
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&dbDictType,NULL);
        server.db[j].expires = dictCreate(&keyptrDictType,NULL);
        server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
        server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].id = j;
    }

    // create timers for serverCron
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }

    // Open the AOF file
    server.aof_fd = open(server.aof_filename,
                               O_WRONLY|O_APPEND|O_CREAT,0644);

    // slow log
    slowlogInit();

    // background system, spawning the thread
    bioInit();
}

Load AOF/RDB file

loadDataFromDisk() is a function called startup to load AOF or RDB file in memory.

void loadDataFromDisk(void) {
    // if AOF is on
    if (server.aof_state == AOF_ON) {
        loadAppendOnlyFile(server.aof_filename)
    } else {
        // if RDF is on
        rdbLoad(server.rdb_filename,&rsi)
    }
}

The detail of AOF or RDB load is out of scope, and I will explain it later.

Jump to event loop

At last, Redis assigns the callbacks beforeSleep and afterSeleep for event loop, then start the loop with asMain(), which is an endless loop for listening and handling events.

aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

Reference