扑街前言:不知道怎么开头,以一个Java开发看金典的C代码还是有点困难,更别说我还是个菜鸟Java,十句代码九句猜,有什么说的不对的后面再修改整理。(如果有一天我能成为大佬的话)
Redis整体是一个key-value结构,简单来说是一个键值数据库(非关系型数据库),大部分情况用于缓存,相比于关系型数据库查询更快,效率更高之类。服务端是有16个数据库,编号0-15,每个都可以存储key-value。这些基础的概念度娘能有详细的内容,所以这篇文章要说的是:如何着手Redis的源码,从哪一步开始看?Redis是如何存储数据的?
从Redis中server.c文件开始解读,找到main()方法,其中会用到一个server对象中的一些方法,而这个server就是redisServer(Globals)服务器端对象。
main方法引用:
int main(int argc, char **argv) {
struct timeval tv;
int j;
#ifdef REDIS_TEST
if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {
return zipmapTest(argc, argv);
} else if (!strcasecmp(argv[2], "sha1test")) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], "sds")) {
return sdsTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
return crc64Test(argc, argv);
}
return -1;
}
#endif
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif
setlocale(LC_COLLATE,"");
zmalloc_enable_thread_safeness();
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
redisServer来源:
struct redisServer server;
redisServer中有大量的成员属性,最重要的是redisDb服务端数据库,默认为16个(这个内容在server.h文件中)
struct redisServer {
pid_t pid;
char *configfile;
char *executable;
char **exec_argv;
int hz;
redisDb *db;
dict *commands;
dict *orig_commands;
aeEventLoop *el;
unsigned lruclock:LRU_BITS;
int shutdown_asap;
int activerehashing;
char *requirepass;
char *pidfile;
int arch_bits;
int cronloops;
char runid[CONFIG_RUN_ID_SIZE+1];
int sentinel_mode;
redisDb服务端数据库中重点是dict哈希字典,学习dict可以类比Java的Map对象
typedef struct redisDb {
dict *dict;
dict *expires;
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
struct evictionPoolEntry *eviction_pool;
int id;
long long avg_ttl;
} redisDb;
详细说下dict哈希字典对象(用Java解释,C的解释我也不会)
typedef struct dict {
// 特定于类型的处理函数
dictType *type;
// 类型处理函数的私有数据
void *privdata;
// 哈希表(2 个)
dictht ht[2];
// 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行
int rehashidx;
// 当前正在运作的安全迭代器数量
int iterators;
} dict;
- dictType *type:一个指向dictType结构的指针(type)。它通过自定义的方式使得dict的key和value能够存储任何类型的数据。
- void *privdata:一个私有数据指针(privdata)。由调用者在创建dict的时候传进来。
- dictht ht[2]:两个哈希表(ht[2])。只有在rehash的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据。上图表示的就是rehash进行到中间某一步时的情况。
- int rehashidx:当前rehash索引(rehashidx)。如果rehashidx = -1,表示当前没有在rehash过程中;否则,表示当前正在进行rehash,且它的值记录了当前rehash进行到哪一步了。代表数组的下标。
- int iterators:当前正在进行遍历的iterator的个数。这不是我们现在讨论的重点,暂时忽略。
1、dictType的结构包含若干函数指针,用于dict的调用者对涉及key和value的各种操作进行自定义,主要内容:
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;
- hashFunction:对key进行哈希值计算的哈希算法。
- keyDup和valDup:分别定义key和value的拷贝函数,用于在需要的时候对key和value进行深拷贝,而不仅仅是传递对象指针。
- keyCompare:定义两个key的比较操作,在根据key进行查找时会用到。
- keyDestructor和valDestructor,分别定义对key和value的析构函数。
- 私有数据指针(privdata)就是在dictType的某些操作被调用时会传回给调用者。
2、dictht(dict hash table)哈希表
typedef struct dictht {
// 哈希表节点指针数组(俗称桶,bucket)
dictEntry **table;
// 指针数组的大小
unsigned long size;
// 指针数组的长度掩码,用于计算索引值
unsigned long sizemask;
// 哈希表现有的节点数量,used / size = 装载因子,这个比值越大,哈希值冲突概率越高。
unsigned long used;
} dictht;
这其中dictEntry哈希表节点,key的哈希值最终映射到这个数组的某个位置上(对应一个bucket)。如果多个key映射到同一个位置,就发生了冲突,那么就拉出一个dictEntry链表。
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
上述就是Redis对应key-value的基本存值方法,总的来说就是通过key值进行HashFunction得到一个数字,然后存入0号哈希表,而哈希表是基于数组构建的,key值经过HashFunction得到的数字就是这个数组的下标索引,通过下标索引存入value。



