剖析Redis源码-01目录结构

一、源码地址

https://github.com/antirez/redis

二、源码整体分布

redis通常被称为数据结构服务器。这意味着redis通过一组命令提供对可变数据结构的访问,这些命令使用带有TCP套接字和简单协议的服务器客户机模型发送。因此,不同的进程可以以共享的方式查询和修改相同的数据结构。


主要包含以下几部分:

  • src:C语言编写的Redis实现。

  • tests:Tcl框架实现的单元测试。(Tcl框架参考:https://www.tcl.tk/community/tcl2017/assets/talk81/Slides.pdf)

  • deps:Redis用到的库。

三、server.h

理解一段程序是如何工作的,最好的方式就是理解它是如何使用数据结的。所以首先从server.h入手。
所有的服务配置和公共配置都是在名为server的全局配置中定义的,几个重点文件如下:

  • server.db 是一组Redis数据库。

  • server.commands 是一个命令表。

  • server.clients 是客户端到服务端的链接列表。

  • server.master 是一个特殊的客户端,如果实例是副本,则返回master。

Redis另一个重要的数据结构是一个客户端的定义。过去它被称为 redisClient,现在叫作 client。此结构有很多字段,下面列举一些重要的:

struct client{
    int fd;
    sds querybuf;
    int argc;
    robj **argv;
    redisDb *db;
    int flags;
    list *reply;
    char buf[PROTO_REPLY_CHUNK_BYTES];
    ... many other fields ...
}
  • fd:客户端套接字文件描述符。

  • argc:当前命令参数的个数。

  • argv:当前命令的参数。

  • querybuf:累积来自客户端的请求。

  • reply:发送给客户端的响应对象列表。

  • buf:响应缓冲数组。

命令行参数被描述为 robj结构,下面来看下完整的robj结构,它定义了一个Redis对象:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

基本上这个结构可以代表redis所有的基本数据类型,如字符串、列表、集合、有序集等等。

  • type:意味着它能够知道对象的数据类型。

  • refcount:以便同一个对象可以在多个位置引用,而无需多次分配它。

  • ptr:指向对象实际值的指针。即使同一类型也可能不同,取决于encoding的值。

四、server.c

main()函数是redis服务的入口,以下是启动reids服务重要的几个步骤:

  • initServerConfig() :设置server结构的默认值。

  • initServer():分配操作所需的数据结构,设置监听套接字等等。

  • aeMain():启动用于侦听新链接的事件循环。

事件循环定期调用两个函数:

  • serverCron():定期调用(根据server.hz的频率),并且执行必须定期执行的任务,例如检查超时客户端。

  • beforeSleep():每次触发事件循环时都会调用,redis会提供一些请求,并返回到事件循环中。

在server.c中,可以找到处理redis服务器其他重要事务的代码:

  • call():用于在给定客户端的上下文中调用给定命令。

  • activeExpireCycle():handles eviciton of keys with a time to live set via the EXPIRE command.

  • freeMemoryIfNeeded():当应执行新的写入命令,redis内存不足时根据maxmemory指令调用。

  • 全局变量redisCommandTable定义了所有Redis命令,命令的名称,命令的实现,所需参数的个数,以及每个命令所需的其他属性。

五、networking.c

这个文件定义了客户端的所有的I/O函数,masters and replicas:

  • createClient():分配并初始化一个新客户端。

  • 形如addReply*()的函数被用于命令实现,以便数据附加到客户端结构,该结构将作为对已执行的给定命令的答复发送给客户机。

  • writeToClient():将输出缓冲区中挂起的数据传输到客户端,并由可写事件处理程序sendreplytoclient()调用。

  • readQueryFromClient():可读的事件处理程序,并将从客户端读取的数据累积到查询缓冲区中。

  • processInputBuffer():根据redis协议解析客户端查询缓冲区的入口点。一旦准备好处理命令,它将调用在server.c中定义的processCommand(),以便实际执行该命令。

  • freeClient():解除分配、断开连接并删除客户端。

六、aof.c and rdb.c

从这些文件的名称可以猜到,这些文件实现了redis的RDB和AOF持久性。redis使用基于fork()系统调用的持久性模型来创建一个线程,该线程具有与主redis线程相同的(共享)内存内容。这个辅助线程将内存的内容转储到磁盘上。RDB.C使用它在磁盘上创建快照,AOF.C使用它在仅追加文件太大时执行AOF重写。

AOF.C内部的实现还有一些额外的功能,以便实现一个API,该API允许在客户端执行新命令时将新命令追加到AOF文件中。

在server.c中定义的call()函数负责调用函数,这些函数将把命令写入AOF。

七、db.c

某些redis命令操作特定的数据类型,其他命令则是常规命令。通用命令的例子有del和expire。它们操作键,而不是特定的值。所有这些通用命令都在db.c中定义。

此外,db.c实现了一个API,以便在不直接访问内部数据结构的情况下对redis数据集执行某些操作。

在许多命令实现中使用的db.c中最重要的函数如下:

  • lookupkeyread()和lookupkeywrite()用于获取指向与给定键关联的值的指针,如果键不存在,则为空。

  • dbad()及其更高级别的对应项setkey()在redis数据库中创建一个新的密钥。

  • dbdelete()删除一个键及其关联值。

  • emptydb()删除整个单个数据库或定义的所有数据库。

文件的其余部分实现了向客户机公开的通用命令。

八、object.c

已经描述了定义redis对象的robj结构。在object.c中,所有的函数都在基本级别上与redis对象一起操作,比如分配新对象、处理引用计数等。此文件中的显著功能:

  • incrrefcount()和decrrefcount()用于增加或减少对象引用计数。当它下降到0时,对象最终被释放。

  • createObject()分配新对象。还有专门的函数来分配具有特定内容的字符串对象,如createStringObjectfromLongLong()和类似的函数。

此文件还实现了OBJECT命令。

九、replication.c

这是Redis中最复杂的文件之一,建议您在熟悉其余代码库之后才能访问它。在这个文件中,实现了redis的master和replica角色。

此文件中最重要的功能之一是replicationFeedSlaves(),它向表示连接到主服务器的副本实例的客户端写入命令,以便副本可以获得客户端执行的写入操作:这样,它们的数据集将保持与主服务器中的数据集的同步。

此文件还实现同步和psync命令,用于在主服务器和副本之间执行第一次同步,或者在断开连接后继续复制。

十、其他C文件

  • t_ hash.c, t_ list.c, t_ set.c, t_ string.c, t_ zset.c 包含redis数据类型的实现。对于这些数据类型,他们都实现了一个API接口,用于访问给定的数据类型或客户端等命令。

  • ae.c实现了redis事件循环,它是一个独立的库,易于阅读和理解。

  • sds.c是redis的字符串库, http://github.com/antirez/sds 获得更多信息。

  • anet.c是一个库,与内核公开的原始接口相比,它以更简单的方式使用POSIX网络。

  • dict.c是一个非阻塞哈希表的实现,它以增量方式重新刷新。

  • scripting.c实现Lua脚本。它完全独立于Redis实现的其他部分,并且非常简单,如果您熟悉LuaAPI,就可以理解它。

  • cluster.c实现了redis集群。大概只有在非常熟悉REDIS代码的其余部分之后才好阅读。如果你想阅读集群,请先阅读https://redis.io/topics/cluster-spec。

十一、剖析redis命令

所有redis命令的定义如下:

void foobarCommand(client *c) {
    printf("%s",c->argv[1]->ptr); /* Do something with the argument. */
    addReply(c,shared.ok); /* Reply something to the client. */
}

然后在service.c的命令表中引用该命令:

{"foobar",foobarCommand,2,"rtF",0,NULL,0,0,0,0,0},

上面示例中2表示命令接受的参数数目,而”rtF”是命令标志,如server.c中的命令表top comment中所述。

在命令以某种方式运行之后,它会向客户机返回一个回复,通常使用addreply()或networking.c中定义的类似函数。

redis源代码中有大量的命令实现,可以作为实际命令实现的示例。编写一些toy命令是熟悉代码库的一个很好的练习。

暂无评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

© 2018-2019 惜春令 京ICP备18010644号 网站地图