
Redis looks like it's doing a long list of unrelated little things when it boots up, but if you read through in order, it follows a fairly clear pipeline: build the command table and parse configuration, set up memory and event handling, then enter the main loop and start serving requests. Here is that pipeline in the order the code executes it.
Configuration initialization
The first thing the service does is set up its configuration, which splits into building the command table and loading the config file.
ACL permissions
ACL information is loaded from redis.conf and determines which commands a logged-in user is allowed to run.
The command table
populateCommandTable() loads the entries of redisCommandTable into the server.commands dictionary, where they are looked up when a command is executed. Frequently used commands are also given a dedicated server field so later lookups can be skipped. Each entry looks like this:
struct redisCommand redisCommandTable[] = {
{"module",moduleCommand,-2,
"admin no-script",
0,NULL,0,0,0,0,0,0},
{"get",getCommand,2,
"read-only fast @string",
0,NULL,1,1,1,0,0,0},
/* Note that we can't flag set as fast, since it may perform an
* implicit DEL of a large key. */
{"set",setCommand,-3,
"write use-memory @string",
0,NULL,1,1,1,0,0,0},
{"setnx",setnxCommand,3,
"write use-memory fast @string",
0,NULL,1,1,1,0,0,0},
}
The fields mean the following:
- name — the command's name.
- proc — the function that handles the command.
- arity — the number of arguments; -N means greater than or equal to N.
- sflags — command flags that identify the command type (read, write, admin, and so on).
- flags — a bitmask computed from sflags.
- get_keys_proc — an optional function used when the next three fields can't describe which arguments are keys.
- first_key_index — the position of the first key argument.
- last_key_index — the position of the last key argument.
- key_step — the step between keys; MSET uses a key_step of 2 because its arguments come as key, value, key, value.
- microseconds — the number of microseconds needed to execute the command.
- calls — the total number of times the command has been called.
A handful of common commands are resolved ahead of time during startup so that the lookup doesn't have to be repeated every time:
server.delCommand = lookupCommandByCString("del");
server.multiCommand = lookupCommandByCString("multi");
server.lpushCommand = lookupCommandByCString("lpush");
server.lpopCommand = lookupCommandByCString("lpop");
server.rpopCommand = lookupCommandByCString("rpop");
server.zpopminCommand = lookupCommandByCString("zpopmin");
server.zpopmaxCommand = lookupCommandByCString("zpopmax");
server.sremCommand = lookupCommandByCString("srem");
server.execCommand = lookupCommandByCString("exec");
server.expireCommand = lookupCommandByCString("expire");
server.pexpireCommand = lookupCommandByCString("pexpire");
server.xclaimCommand = lookupCommandByCString("xclaim");
server.xgroupCommand = lookupCommandByCString("xgroup");
server.rpoplpushCommand = lookupCommandByCString("rpoplpush");
Sentinel mode
When sentinel is enabled, startup also initializes its parameters. This happens in two parts: initSentinelConfig() sets up things like the port, while initSentinel() removes the basic commands of a regular Redis instance and installs sentinel's own commands:
struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
{"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
{"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0}
}
RDB / AOF checks
Before startup completes, the integrity of the RDB or AOF file is checked through redis_check_rdb_main() / redis_check_aof_main().
Command-line arguments
Simple arguments such as -v/--version and -h/--help are handed to the corresponding handler, which prints the information. If a different config file is specified, server.exec_argv is modified; any other arguments are converted into strings and appended to the config file — --port 6380, for example, becomes port 6380\n. Redis then calls loadServerConfig() to load the configuration, and this step overwrites the default values initialized earlier.
Initializing Redis
Shared objects
createSharedObjects() creates a set of shared objects and stores them in the global shared variable. Different commands may return the same value (error messages, for instance), and creating these objects up front means they don't have to be allocated again each time they're returned. The design trades a little extra startup time for lower latency at runtime.
The event loop
initServer() calls aeCreateEventLoop() (in ae.c) to create the loop and stores the result in the el member of server. Redis picks a different implementation per platform: epoll on Linux, kqueue on BSD, and select when neither is available. It uses this loop to poll for new connections and I/O events, responding as soon as something arrives.
Listening on the port
Port listening is done in anetUnixServer() through anetListen().
The LRU key pool
evictionPoolAlloc() initializes the LRU key pool; Redis's key expiration strategy is an approximation of LRU:
void evictionPoolAlloc(void) {
struct evictionPoolEntry *ep;
int j;
ep = zmalloc(sizeof(*ep)*EVPOOL_SIZE);
for (j = 0; j < EVPOOL_SIZE; j++) {
ep[j].idle = 0;
ep[j].key = NULL;
ep[j].cached = sdsnewlen(NULL,EVPOOL_CACHED_SDS_SIZE);
ep[j].dbid = 0;
}
EvictionPoolLRU = ep;
}
Timed tasks
Next, aeCreateTimeEvent() (also in ae.c) registers an event that repeatedly invokes serverCron(), which by default runs every 100 milliseconds.
/* Create the timer callback, this is our way to process many background
* operations incrementally, like clients timeout, eviction of unaccessed
* expired keys and so forth. */
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}
The main loop
Finally, the program calls aeMain() and enters the main loop, where the other loop events get dispatched as well:
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);
}
}
At that point the startup sequence is done, and Redis begins accepting connections and processing commands.