sds 字符串
char*表示,文件sds.h。定义
// __attribute__ ((__packed__)) 取消结构体字节对齐,按紧凑排列
struct __attribute__ ((__packed__)) sdshdr8 { // len和alloc是uint8_t类型
uint8_t len; /* used 已使用长度,不包括\0 */
uint8_t alloc; /* excluding the header and null terminator 已分配长度*/
unsigned char flags; /* 3 lsb of type, 5 unused bits 低三位表示类型,高5位没有使用*/
char buf[]; /*保存的字符串内容,以'\0'结束 */
};
list
list实现为一个双向链表
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr); // 函数实现
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned long len;
} list;
迭代器包含一个指针和一个方向
typedef struct listIter {
listNode *next;
int direction;
} listIter;
set
无序集合
set正常使用hash table实现;但当集合中只包含整数元素,且元素不多时,redis使用intset作为set的底层实现
set使用dict实现,key是sds string,value没有使用
intset类型:
typedef struct intset {
uint32_t encoding; // 编码方式,使用16位/32位/64位编码
uint32_t length; // 元素数量
int8_t contents[]; // 数据
} intset;
intset类型是用固定长度表示每个key,encoding标识了编码方式(是使用64位呢还是32位还是16位表示),contents中为有序排列的set元素值,在intset中查找元素使用二分查找,效率更高
创建什么样类型的set判断:
robj *setTypeCreate(sds value) {
// 可以用longlong表示,就创建intset,否则创建一般的dict
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
return createIntsetObject();
return createSetObject();
}
在使用intset时,有两种情况会转换类型到hash table:1.新添加元素不能转成int,把intset转成HT,2.intset的元素数目>server.set_max_intset_entries了
intset添加元素:
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value); // 编码位数,最长64,最小16
uint32_t pos;
if (success) *success = 1;
/* Upgrade encoding if necessary. If we need to upgrade, we know that
* this value should be either appended (if > 0) or prepended (if < 0),
* because it lies outside the range of existing values. */
if (valenc > intrev32ifbe(is->encoding)) { // 编码长度不够用了,需要升级一下
/* This always succeeds, so we don't need to curry *success. */
// 升级encoding类型,新元素append到最后端或者最前端
return intsetUpgradeAndAdd(is,value);
} else {
/* Abort if the value is already present in the set.
* This call will populate "pos" with the right position to insert
* the value when it cannot be found. */
// 使用二分查找找到新插入的value应该在的位置pos,已经有这个value了,直接返回
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
}
is = intsetResize(is,intrev32ifbe(is->length)+1);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1); // 使用memmove拷贝字节
}
_intsetSet(is,pos,value); // 插入新元素
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
hash
字典 结构:
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 头插,开链法解决冲突
} dictEntry;
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table; // hash表
unsigned long size; // bucket数量
unsigned long sizemask;
unsigned long used; // 已使用的key数量
} dictht;
typedef struct dict {
dictType *type; // 函数实现
void *privdata; // 数据
dictht ht[2]; // ht[1]用于rehash期间的临时使用
long rehashidx; /* rehashing 没有在进行时rehashidx == -1,正在rehash表示rehash进行到的old table的idx
*/
unsigned long iterators; /* number of iterators currently running,dict扫描的safe iterator,等于0的时候才能进行rehash */
} dict;
struct dict为redis的hash结构实现,使用双buffer(ht[2])解决rehash问题,dictht中,table为线型表,包含了size个bucket,每个bucket里面是一个dictEntry结构,dictEntry中next为指向同bucket下一个元素的指针,可以发现redis的hash表解决冲突方法为开链法
添加元素
// 如果key已经存在了,返回NULL,用existing表示这个key所在的entry
// key不存在,添加上去,并且返回这个新添加key的entry
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
long index;
dictEntry *entry;
dictht *ht;
if (dictIsRehashing(d)) _dictRehashStep(d); // 如果正在rehash
/* Get the index of the new element, or -1 if
* the element already exists. */
// 获取新key的index,如果这个key已经存在了返回-1
// 在_dictKeyIndex中可能会启动expand
if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
return NULL;
/* Allocate the memory and store the new entry.
* Insert the element in top, with the assumption that in a database
* system it is more likely that recently added entries are accessed
* more frequently. */
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0]; // 如果hash表正在rehash,就使用d->ht[1],否则使用d->ht[0]
entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index]; // 新元素插到头部去
ht->table[index] = entry;
ht->used++;
/* Set the hash entry fields. */
dictSetKey(d, entry, key);
return entry;
}
增量rehash
在添加元素过程中,如果发现hash表的used/size >= 1了,那么会启动rehash。hash表使用增量rehash的方式,每次调用添加或查找元素的函数会执行rehash一部分元素(比如1个);新hash表的table每次以*2扩充
增量rehash的执行过程:
// 增量rehash,将rehash向前推进n步。n=1.
// 返回1表示rehash没有完成,返回0表示本次rehash全部完成并切换到ht[0]了
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0; // 没有在rehash,直接返回
while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde;
/* Note that rehashidx can't overflow as we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size > (unsigned long)d->rehashidx);
while(d->ht[0].table[d->rehashidx] == NULL) { // 遇到空的bucket跳过,遇到10个空的bucket之后直接返回
d->rehashidx++;
if (--empty_visits == 0) return 1;
}
de = d->ht[0].table[d->rehashidx]; // 指向bucket=d->rehashidx的第一个节点
/* Move all the keys in this bucket from the old to the new hash HT */
// 老hash表的d->rehashidx个bucket所有节点重新hash到新hash表
while(de) {
uint64_t h;
nextde = de->next;
/* Get the index in the new hash table */
h = dictHashKey(d, de->key) & d->ht[1].sizemask; // 获取在新hash表中的index
de->next = d->ht[1].table[h];
d->ht[1].table[h] = de;
d->ht[0].used--;
d->ht[1].used++;
de = nextde;
}
d->ht[0].table[d->rehashidx] = NULL; // 老hash表的节点指向null
d->rehashidx++;
}
/* Check if we already rehashed the whole table... */
// 切换ht,仍然ht[0]为可用table
// 释放ht[0]的table并重新指向ht[1]的table,ht[1]的table指向null
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* More to rehash... */
return 1;
}
查找元素
dictEntry *dictFind(dict *d, const void *key)
{
dictEntry *he;
uint64_t h, idx, table;
if (dictSize(d) == 0) return NULL; /* dict is empty */
if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key);
// 在dictAddRaw中,如果正在rehash,那么直接往ht[1]添加元素,所以新添加元素在ht[0]没有,要同时查找ht[0]和ht[1]
for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx];
while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key))
return he;
he = he->next;
}
if (!dictIsRehashing(d)) return NULL; // 没有在rehash的话,就不找ht[1]了
}
return NULL;
}