环境
- redis1.0
redis早期版本代码不多,但包括redis核心功能,如事件驱动、线程模型、数据模型,适合阅读研究其核心结构
核心文件
- anet
- anet负责基本的socket操作
- 包括连接建立、读、写,服务端端口监听、接收连接等
- 底层采用select多路复用器
- ae
typedef struct aeEventLoop {
long long timeEventNextId;
aeFileEvent *fileEventHead;
aeTimeEvent *timeEventHead;
int stop;
} aeEventLoop;
- ae为事件驱动处理库
- 事件包括io事件以及定时事件
- redis
redis主要负责客户端连接建立、命令处理、定时任务处理
线程模型
- 网络编程主要包括建立与客户端的连接、读写事件、业务处理三个部分,根据实际的业务情况,可以把三部分分别或组合放在不同的线程中进行处理,由此产生了不同的线程模型。而redis采用单线程处理了连接建立、io读写、业务处理甚至还包括定时任务,这一切都基于其事件驱动模型。
- aeEventLoop包括io(aeFileEvent)和定时任务(aeTimeEvent)两类事件,每类事件为一个链表队列,程序采用循环遍历事件队列进行事件处理。
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
数据模型
数据库
typedef struct redisDb {
dict *dict;
dict *expires;
int id;
} redisDb;
- redis默认有16个数据库,每个数据库包括两个hash表,一个用于存储无过期时间的kv,另一个用于存储有过期时间的kv
kv
typedef struct redisObject {
void *ptr;
int type;
int refcount;
} robj;
● kv都为redisObject类型,该结构包括数据、数据的类型(String、List、Hash、Set)、引用数量,只是key的数据类型为String
主要数据结构
- adlist
双向链表结构
- dict
- dict为hash表,内部采用数组存储元素
- 出现hash冲突时,采用链表存储
- 每次扩容为原大小的2倍,实际大小为2n
- sds
字符串操作库
核心方法
程序入口main
int main(int argc, char **argv) {
initServerConfig();
if (argc == 2) {
ResetServerSaveParams();
loadServerConfig(argv[1]);
} else if (argc > 2) {
fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf]\n");
exit(1);
} else {
redisLog(REDIS_WARNING,"Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'");
}
initServer();
if (server.daemonize) daemonize();
redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__
linuxOvercommitMemoryWarning();
#endif
if (rdbLoad(server.dbfilename) == REDIS_OK)
redisLog(REDIS_NOTICE,"DB loaded from disk");
if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event");
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
}
- 程序入口为redis.c的main方法
- initServer方法中主要包括绑定端口、建立网络监听、入队定时任务事件
- 开启事件循环
建立连接
static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
char cip[128];
redisClient *c;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);
cfd = anetAccept(server.neterr, fd, cip, &cport);
if (cfd == AE_ERR) {
redisLog(REDIS_DEBUG,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_DEBUG,"Accepted %s:%d", cip, cport);
if ((c = createClient(cfd)) == NULL) {
redisLog(REDIS_WARNING,"Error allocating resoures for the client");
close(cfd); /* May be already closed, just ingore errors */
return;
}
/* 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 nonblocking
* mode and we can send an error for free using the Kernel I/O */
if (server.maxclients && 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 */
(void) write(c->fd