Redis源码解析(1)——源码目录介绍

概念

      redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

开始

      这里我使用的是redis-2.2.2.tar.gz版本的redis(下载地址)首先要对它进行安装,这里我选择使用cygwin工具进行安装,加入我把该压缩包放在F盘下,使用cygwin工具进行shell命令:

1
2
3
tar xzf redis-2.2.2.tar.gz
cd redis-2.2.2
make

     这里的make实际上操作的是Makefile文件,Makefile按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个shell脚本一样,其中也可以执行操作系统的命令。

浏览下Redis根目录中的Makefile文件:

      通过make命令可以执行“cd src && make all”。而此时的make all实际上已经开始执行src目录中的Makefile文件。这个文件比较复杂,大致就是将一系列的c文件以及h文件链接起来,通过cc/gcc编译器将文件生成目标文件o,接着将相应的o目标文件在通过编译器生成exe文件,当你编译完毕后,在src的目录上将产生5个exe文件:

image

redis-benchmark.exe:用于做性能测试;

redis-check-aof.exe:更新日志检查;

redis-check-dump.exe:用于本地数据库检查;

redis-cli.exe:客户端程序;

redis-server.exe:服务端程序;

具体用法这里不多说了,可以参考(http://www.cnblogs.com/daizhj/articles/1956681.html

      现在来看下src包含的文件(我按照首字母顺序来讲):

adlist.h/adlist.c:用于对list的定义,它是个双向链表结构,从头文件可以找到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// list节点
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;
 
// list迭代器
typedef struct listIter {
    listNode *next;
    int direction;
} listIter;
 
// list数据结构
typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    unsigned int len;
} list;

在ListNode节点下包含prev指针和next指针,说明它通过指针将节点进行双向链接。

并且从adlist.h的头文件可以找到非常丰富的方法声明,包括list创建,list释放,list头部/尾部添加节点等等,具体在后面的系列会做出介绍。

ae.h/ae.c:用于Redis的事件处理,包括句柄事件和超时事件。

在ae.c中的头部可以发现:

1
2
3
4
5
6
7
8
9
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
    #ifdef HAVE_KQUEUE
    #include "ae_kqueue.c"
    #else
    #include "ae_select.c"
    #endif
#endif

      在网络相关操作中,定义了一组公共操作接口:aeApiCreate,aeApiFree,aeApiAddEvent,aeApiDelEvent,aeApiPoll,aeApiName方法。在ae_epoll.c、ae_kqueue.c和ae_select.c中,分别实现了基于epoll/kqueue和select系统调用的接口。系统调用的选择顺序依次为epoll,kqueue,select。

anet.h/anet.c:这两个文件非常重要,作为Server/Client通信的基础封装,包括anetTcpServer,anetTcpConnect,anetTcpAccept,anetRead,anetWrite等等方法。

aof.c:aof,全称为append only file,作用就是记录每次的写操作,在遇到断电等问题时可以用它来恢复数据库状态。但是他不是bin的,而是text的。一行一行,写得很规范.如果你是一台redis,那你也能人肉通过它恢复数据。

config.h/config.c:用于将配置文件redis.conf文件中的配置读取出来的属性通过程序放到server对象中。在main函数(server服务主入口点处)可以发现里面调用loadServerConfig(char *filename)方法,这个方法就是使用config.c里面的方法实现。具体会在后面的系列中详细介绍。

db.c:对于Redis内存数据库的相关操作。

debug.c:用于调试使用。

dict.h/dict.c:也是很重要的两个文件,主要对于内存中的hash进行管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct dictEntry {
    void *key;
    void *val;
    struct dictEntry *next;
} dictEntry;
 
typedef struct dictType {
    unsigned int (*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;
typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

这里dictEntry作为一个dict字段结构,里面包括key以及value,已经指向下一个dictEntry的指针。dictType作为一些dict的操作结构。dict作为一个hash结构。后面的文章会具体介绍。

fmacros.h:用于Mac下的兼容性处理。

help.h:辅助于命令的提示信息,作用于redis-cli.exe可执行文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct commandHelp {
  char *name;
  char *params;
  char *summary;
  int group;
  char *since;
} commandHelp[] = {
    "APPEND",
    "key value",
    "Append a value to a key",
    1,
    "1.3.3" },
    "AUTH",
    "password",
    "Authenticate to the server",
    8,
    "0.08" },
    "BGREWRITEAOF",
    "-",
    "Asynchronously rewrite the append-only file",
    9,
    ....
};

intset.h/intset.c:整数范围内的使用set,并包含相关set操作。

lzf.h/lzf_c.c/lzf_d.c/lzfP.h:对于本地数据库的保存,使用的是LZF压缩算法,很神奇,算法只有200-300行的代码。

multi.c:用于事务处理操作。请看这样的一个例子:

image

通过执行exec,可以提交整个事务过程,如果你想撤销整个事务过程,你可以使用discard命令:

image

可以发现get age已经取不到值了,说明discard命令让事务失效。

networking.c:网络协议传输方法定义相关的都放在这个文件里面了。包括让Client连接上Server,让Slave挂接到Master,已经Server/Client之间的信息交互的实现等等。

object.c:用于创建和释放redisObject对象,redisObject结构为:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct redisObject {
    unsigned type:4;
    unsigned storage:2;     /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */
    unsigned encoding:4;
    unsigned lru:22;        /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
    /* VM fields are only allocated if VM is active, otherwise the
     * object allocation function will just allocate
     * sizeof(redisObjct) minus sizeof(redisObjectVM), so using
     * Redis without VM active will not have any overhead. */
} robj;

pqsort.h/pqsort.c/sort.c:关于排序算法,sort.c具体作为Redis场景下的排序实现。

pubsub.c:用于订阅模式的实现,有点类似于Client广播发送的方式。

rdb.c:对于Redis本地数据库的相关操作,默认文件是dump.rdb(通过配置文件获得),包括的操作包括保存,移除,查询等等。

redis-benchmark.c:用于redis性能测试的实现。请看main方法以下设置:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
config.debug = 0;
config.numclients = 50;
config.requests = 10000;
config.liveclients = 0;
config.el = aeCreateEventLoop();
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
config.randomkeys = 0;
config.randomkeys_keyspacelen = 0;
config.quiet = 0;
config.loop = 0;
config.idlemode = 0;
config.latency = NULL;
config.clients = listCreate();
config.hostip = "127.0.0.1";
config.hostport = 6379;
config.hostsocket = NULL;
 
parseOptions(argc,argv);
config.latency = zmalloc(sizeof(long long)*config.requests);

默认性能测试中的客户端数量为50个,并行发送的请求有10000条,你也可以通过redis-benchmark命令行参数进行设置。

redis-check-aof.c:用于更新日志检查的实现。

redis-check-dump.c:用于本地数据库检查的实现。

redis-cli.c:客户端程序的实现。具体会在后面的文章详细介绍。

redis.h/redis.c:服务端程序的实现。具体会在后面的文章详细介绍。

release.h/release.c:用于发布使用。

replication.c:用于主从数据库的复制操作的实现。


sds.h/sds.c:用于对字符串的定义,从头文件可以找到:

1
2
3
4
5
6
//字符串
struct sdshdr {
    int len;
    int free;
    char buf[];
};

还可以看到对于字符串的相关操作,包括复制,连接,清零等等。

sha1.h/sha1.c:有关于sha算法的实现。

solarisfixes.h:Solaris系统的兼容性实现。

syncio.c:用于同步Socket和文件I/O操作。

t_hash.c/t_list.c/t_set.c/t_string.c/t_zset.c:hash,list,set,string,zset在Server/Client中的应答操作。主要通过redisObject进行类型转换。

testhelp.h:一个C风格的小型测试框架。

util.c:关于通用工具的方法实现。

version.h:Redis版本号定义。

vm.c:关于虚拟内存的管理实现。

zipmap.h/zipmap.c:zipmap是一个类似于hash的存储对象。在新建一个hash对象时开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销,如果field或者value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现。

ziplist.h/ziplist.c:ziplist是一个类似于list的存储对象。它的原理类似于zipmap。

zmalloc.h/zmalloc.c:关于Redis的内存分配的封装实现。

原文地址:https://www.cnblogs.com/zxtceq/p/5633416.html