经过前两篇的介绍,我们对整个redis的动作流程已经有比较清晰的认识。
接下来就是到具体的命令处理方式的理解了,想来我们用这些工具的意义也是在此。虽然没有人觉得,一个set/get方法会有难度,但是我们毕竟不是很清楚,否则也不至于在谈到深处就懵逼了。
我觉得本文的一个重要意义就是: 让set/get还原成它本来样子,和写"hello world"一样简单。
框架性质的东西,我们前面已经讲解,就直接进入主题: set/get 的操作。
set/get 对应的两个处理函数 (redisCommand) 定义是这样的:
// rF 代表 getCommand 是只读命令,又快又准,时间复杂度 O(1)或者O(log(n))
// wm 代表 setCommand 是个写命令,当心空间问题
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0}
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0}
所以,我们只要理解了, setCommand,getCommand 之后,就可以完全自信的说,set/get 就是和 "hello world" 一样简单了。
零、hash 算法
很显然,kv型的存储一定是hash相关算法的实现。那么redis中如何使用这个hash算法的呢?
redis 中许多不同场景的hash算法,其原型是在 dictType 中定义的。
typedef struct dictType {
// hash 算法原型
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;
针对大部分场景,我们的key一般都是 string 类型的,但是还是会稍微有不一样的。这里我们就两个场景来说明下:
1. 命令集构建的hash算法
即是 server.commands 中的key的hash算法,这里元素是有限的。其定义如下:
/* Command table. sds string -> command struct pointer. */
dictType commandTableDictType = {
// 即 dictSdsCaseHash 是 command 的hash算法实现
dictSdsCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */
NULL /* val destructor */
};
// server.c, 不区分大小写的key hash
unsigned int dictSdsCaseHash(const void *key) {
return dictGenCaseHashFunction((unsigned char*)key, sdslen((char*)key));
}
// dict.c, 不区分大小写的key-hash算法: hash * 33 + c
/* And a case insensitive hash function (based on djb hash) */
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) {
// static uint32_t dict_hash_function_seed = 5381;
unsigned int hash = (unsigned int)dict_hash_function_seed;
while (len--)
hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
return hash;
}
2. 针对普通kv查询的hash算法
整个nosql就是kv的增删改查,所以这是个重要的算法。
/* Db->dict, keys are sds strings, vals are Redis objects. */
dictType dbDictType = {
// k 的hash算法实现
dictSdsHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
dictSdsDestructor, /* key destructor */
dictObjectDestructor /* val destructor */
};
// server.c, 调用 dict 的实现
unsigned int dictSdsHash(const void *key) {
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
}
// dict.c, 数据key的hash算法,或者通用的 string 的hashCode 算法
/* MurmurHash2, by Austin Appleby
* Note - This code makes a few assumptions about how your machine behaves -
* 1. We can read a 4-byte value from any address without crashing
* 2. sizeof(int) == 4
*
* And it has a few limitations -
*
* 1. It will not work incrementally.
* 2. It will not produce the same results on little-endian and big-endian
* machines.
*/
unsigned int dictGenHashFunction(const void *key, int len) {
/* 'm' and 'r' are mixing constants generated offline.
They're not really 'magic', they just happen to work well. */
uint32_t seed = dict_hash_function_seed;
const uint32_t m = 0x5bd1e995;
const int r = 24;
/* Initialize the hash to a 'random' value */
uint32_t h = seed ^ len;
/* Mix 4 bytes at a time into the hash */
const unsigned char *data = (const unsigned char *)key;
// 核心算法: step1. *m, ^k>>r, *m, *m, ^k, 每4位做一次运算
while(len >= 4) {
uint32_t k = *(uint32_t*)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
/* Handle the last few bytes of the input array */
// step2. 倒数第三位 ^<<16, 第二位 ^<<8, 第一位 ^, 然后 *m
switch(len) {
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0]; h *= m;
};
/* Do a few final mixes of the hash to ensure the last few
* bytes are well-incorporated. */
// step3. 再混合 ^>>13, *m, ^>>15
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return (unsigned int)h;
}
可以看到,针对普通的字符串的hash可是要复杂许多呢,因为这里数据远比 command 的数据多,情况更复杂,这样的算法唯一的目标就是尽量避免hash冲突。(虽然不知道为啥这么干,但它就是牛逼)
redis中还有其他的hash算法,比如dictObjHash,dictEncObjHash, 后续有接触我们再聊。
接下来,我们正式来看看 set/get 到底如何?
一、getCommand 解析
很显然,get 会是个最简单的命令,自然要检软柿子捏了。
// t_string.c
void getCommand(client *c) {
getGenericCommand(c);
}
int getGenericCommand(client *c) {
robj *o;
// 如果在kv里找不到,则直接响应空&