Redis 基本类型及功能

来源:互联网 发布:数据大玩家直播室 编辑:程序博客网 时间:2024/04/29 07:12

一、链表(adlist.h/adlist.c)

实质是一个双端列表

listIter是访问链表的迭代器,指针(next)指向链表的某个结点,direction标示迭代访问的方向(宏AL_START_HEAD表示向前,AL_START_TAIL表示向后)。
typedef struct listIter {
listNode *next;
int direction;
} listIter;

 

二、字符串(sds.h/sds.c)

typedef char *sds;

struct sdshdr {
int len;
int free;
char buf[];
};

在sdsnewlen(const void *init, size_t initlen)中,会分配sizeof(struct sdshdr)+initlen+1的空间,并将*init指向的字符串拷贝到buf中,在buf末尾补上’\0’。但是sdsnewlen()中返回给外部的,只有sdshdr->buf。

size_t sdslen(const sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
return sh->len;
}
此处参数s实际是sdshdr中的buf,通过(s-(sizeof(struct sdshdr))即可得到sdshdr的地址。

 

三、哈希表(dict.h/dict.c)

Redis的哈希表最大的特色就是自动扩容。当它的哈希表容量不够时,可以0/1切换,然后自动扩容。下面具体分析哈希表的实现。
整个哈希系统由结构体dict定义,其中type包含一系列哈希表需要用的函数,dictht类型的数组ht[2]表示两个哈希表实例,由rehashidx指明下一个需要扩容的哈希实例的编号,iterators记录外部使用哈希表的迭代器的数目。
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;
dictht为哈希表具体实现的结构体,table指向哈希中的记录,用数组+开链的形式保存记录;size表示哈希表桶的大小,为2的指数;sizemark=size-1,方便哈希值根据size取模;used记录了哈希表中的记录数目。
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
哈希表使用开链的方式处理冲突,每条记录都是链表中的一个结点。dictType在哈希系统中包含了一系列可由应用程序定义的函数指针,包括Hash函数、Key复制、Value复制、Key比较、Key析构、Value析构,以增加哈希系统的灵活性。 其中系统定义了三种默认的type,表示最常用的三种哈希表。
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;
extern dictType dictTypeHeapStringCopyKey;
extern dictType dictTypeHeapStrings;
extern dictType dictTypeHeapStringCopyKeyValue;
Redis定义了一系列的宏用于操作哈希表,例如设置记录的Value。对外提供的API,除了常规的创建哈希表,增、删、改记录之外,有两类是比较特别的:自动扩容和迭代器。

 

自动扩容
Redis用变量dict_can_resize记录哈希是否可以自动扩容,由两个方法dictEnableResize()和dictDisableResize()设置该变量。应用程序可以使用dictResize()扩容,它首先判断是否允许扩容,及是否正在扩容。若可以扩容,则调用dictExpand()扩容。然后应用程序需要调用dictRehashMilliseconds()启动扩容过程,并指定扩容过程中记录拷贝速度。除了应用程序指定的扩容外,在调用dictAdd()往哈希中添加记录时,系统也会通过调用_dictExpandIfNeeded()判断是否需要扩容。_dictExpandIfNeeded()中,如果正在扩容,则不会重复进行扩容;如果哈希表size=0,即桶数目为0,则扩容到初始大小;否则如果used>=size,并且can_resize==1或used/size超过阀值(默认为5)时,以max(used, size)的两倍为基数,调用dictExpand()扩容。

dictExpand()进行扩容时,会先选择一个满足size需求的2的指数,然后分配内存空间,创建新的哈希表。如果此时ht[0]为空,则直接将哈希表赋值给ht[0];否则,赋值给ht[1],并启动拷贝过程,将ht[0]的记录逐个桶地拷贝到ht[1]中。置rehashidx=0,表明正在扩容,且待拷贝的桶为ht[0]->table[rehashidx]。

dictIsRehashing()通过rehashidx来判断是否正在扩容。这个方法在多处被调用,当dictAdd()往哈希表中添加记录时,也会通过该方法判断是否正在扩容。若正在扩容,则调用_dictRehashStep(),该函数判断,若此时iterators==0,即没有迭代器时,就从ht[0]中拷贝一部分记录到ht[1]。拷贝过程在dictRehash()中完成,该函数返回0时,表示扩容结束,ht[0]中所有记录都已拷贝到ht[1],且rehashidx被置为-1;否则返回1,表示扩容未结束。拷贝过程中,将ht[0]->table[rehashidx]拷贝到ht[1]后,rehashidx++,直到used==0,即所有记录拷贝完成。拷贝一个桶时,需要对桶中所有元素重新求哈希值,然后一个个放入ht[1]中。dictRehash()通过参数,控制每次拷贝的桶的数目。

迭代器
迭代器提供了遍历哈希表中所有元素的方法,通过dictGetIterator()获得迭代器后,使用dictNext(dictIterator *)方法获得下一个元素。当外部持有的迭代器数目不为0时,哈希表会暂停扩容操作。迭代器遍历的过程,从ht[0]开始,依次从第一个桶table[0]开始遍历桶中的元素,然后时table[1], table[2], ..., table[size],若正在扩容,则会继续遍历ht[1]中的桶。遍历桶中元素时,依次访问链表中的每个元素。

 

四、内存(zmalloc.h/zmalloc.h)

先回忆各个系统中常见的内存分配函数:malloc()分配一块指定大小的内存区域,并返回指向区域开头的指针,若分配失败,则返回NULL。
calloc()与malloc()一样,分配一块指定大小的内存区域,成功时返回区域头指针,失败返回NULL。区别在于,calloc()的输入参数为count和size,即分配的项的数目,及每一项的大小。calloc()在成功分配内存空间后,会将空间内所有值置0。realloc()修改已分配的内存块的大小。若已分配的内存块后没有足够的空间用于扩展内存块,则重新申请一块满足需要的内存块,并将旧的数据拷贝到新位置,释放旧的内存块,返回指向新的内存块的指针;否则直接扩展原有的内存块。若分配失败,返回NULL。free()释放已分配的内存块。

Redis在申请内存时,除了申请需要的size外,还会多申请一块定长(PREFIX_SIZE)的区域用于记录所申请的内存块的长度。如果申请成功,Redis会使用宏函数(Redis中为性能考虑,大量使用宏函数)update_zmalloc_stat_alloc(size+PREFIX_SIZE, size)记录申请的内存块的相关信息,以便监控内存使用状况;当内存块被zfree()释放时,根据头部的信息可以快速地获知被释放的内存区域的长度,然后通过宏函数update_zmalloc_stat_free()标记释放。在本身支持malloc_size()的系统中,PREFIX_SIZE等于0.

0 0