Redis的底层数据结构

SDS

定义

从代码中找出原定义

struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
typedef char *sds;

在源码里,还有sdshdr5,sdshdr16,sdshdr32这样的其他定义,其中sdshdr5不使用。在代码里直接使用的,都是自定义的sds类型。这里可以看到,我们使用的sds实际上就是一个char*的指针,具体的使用方式如下:
有 sds s;其中s实际指向的是结构体中的buf。flags中存储了是sdshdr16,还是sdshdr32等信息。通过__attribute__ ((packed))可以取消字节对齐,所以可以很容易的根据sds所指向的buf,来找出其他的len,alloc,flags信息。

给sds分配空间

sds sdsMakeRoomFor(sds s, size_t addlen)
当s需要添加长度addlen的数据时,调用这个函数来重新分配空间

当alloc-len > addlen时,无需分配新空间,直接返回。需要分配空间时,当s的大小在 1Mb 以内的时候,分配(len+addlen)*2的大小。当s的大小在 1Mb 以上的时候,分配(len+addlen)+1MB的大小。
可以看出来实际上是多分配了一些空间的。

主要注意的是,分配空间后要注意是否type需要改变,选择对应的sdshdr16或者sdshdr32等等

List

list的节点构造如下
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

对于一个链表list,使用如下数据结构表示
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;

这里的dup和free和match是有三个函数指针,基本就是让list可以匹配多种类型

dict

redis里字典由hash表实现。ps:在C++里map应该是由红黑树实现,unordered_map由hash表生成

哈希表的构造
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

存储键值对的数据结构
typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

dictEntry用来指向一个[key,val]的结构体,所以table可以类比为一个数组,dictEntry[n]这样的一个构造。代码中的sizemask = size -1,用来计算key应该放在table中的索引index,假设哈希函数是hash,则计算方式为index=hash(key) & sizemask。这个sizemask可以保证index<size。
从上面可以看出来,index的取值是有限的,所以当两个key的index一致时,就会使用链式法来解决哈希冲突的问题。必要时会扩充table,增加size的值。

扩展的方法
static int dictTypeExpandAllowed(dict *d) {
    if (d->type->expandAllowed == NULL) return 1;
    return d->type->expandAllowed(
                    _dictNextPower(d->ht[0].used + 1) * sizeof(dictEntry*),
                    (double)d->ht[0].used / d->ht[0].size);
}

_dictNextPower(d->ht[0].used + 1)是大于d->ht[0].used + 1的最小的2^n

跳跃表

文件位置位于server.h,这一点是与之前的不同

这是跳跃表的每一个节点
typedef struct zskiplistNode {
    sds ele;  //保存的字符串
    double score;  // 相应的分数
    struct zskiplistNode *backward;  //这个是指向前一个节点的指针
    struct zskiplistLevel {
        struct zskiplistNode *forward;  // 后续的节点
        unsigned long span;  //当前节点和后续的节点中间隔了多少个跳跃表节点
    } level[];
} zskiplistNode;

整数集合

集合键在只有整数,且数量不多的情况下,就会使用整数集

typedef struct intset {
    uint32_t encoding;
    uint32_t length; //保存元素的个数
    int8_t contents[];
} intset;

内容存储在contents中,这里存储多少字节的整数类型,是由encoding决定的。我们用一个find的具体代码来分析

/* Determine whether a value belongs to this set */
uint8_t intsetFind(intset *is, int64_t value) {
    uint8_t valenc = _intsetValueEncoding(value);  //用来确定是多少字节的数
    return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}

这里的_intsetValueEncoding(value)会根据value的大小,返回编码方式

/* Search for the position of "value". Return 1 when the value was found and
 * sets "pos" to the position of the value within the intset. Return 0 when
 * the value is not present in the intset and sets "pos" to the position
 * where "value" can be inserted. */
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
    int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
    int64_t cur = -1;
    while(max >= min) {
        mid = ((unsigned int)min + (unsigned int)max) >> 1;
        cur = _intsetGet(is,mid);
        if (value > cur) {
            min = mid+1;
        } else if (value < cur) {
            max = mid-1;
        } else {
            break;
        }
    }
	中间省略了一部分
}

这里其实使用的就是二分查找,我们重点看一下是如何获取值的

/* Return the value at pos, using the configured encoding. */
static int64_t _intsetGet(intset *is, int pos) {
    return _intsetGetEncoded(is,pos,intrev32ifbe(is->encoding));
}

static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
    int64_t v64;
    int32_t v32;
    int16_t v16;

    if (enc == INTSET_ENC_INT64) {
        memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
        memrev64ifbe(&v64);
        return v64;
    } else if (enc == INTSET_ENC_INT32) {
        memcpy(&v32,((int32_t*)is->contents)+pos,sizeof(v32));
        memrev32ifbe(&v32);
        return v32;
    } else {
        memcpy(&v16,((int16_t*)is->contents)+pos,sizeof(v16));
        memrev16ifbe(&v16);
        return v16;
    }
}
其实重点就是这一句memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
通过((int64_t*)is->contents)+pos找到对应的位置

ziplist

ziplist没有相应的结构体,而是使用了宏来进行操作。

ziplist:
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
  4字节		 4字节   2字节     不定    不定        不定    0xFF,一字节
其中zlbytes指ziplist所占的所有字节,zltail用于指向最后一个entry,zllen是entry的个数

entry:
<prevlen> <encoding> <entry-data>
prevlen是前面一个entry所占的字节,大小可以是1字节或者5字节。prevlen<=253时,是1字节,否则为5字节
<prevlen from 0 to 253> <encoding> <entry>
0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry> //后面的4字节用来标识长度
encoding用来标识后面的entry-data是string还是数字。以及保存数据的长度
/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
 * zlbytes*/
#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
int ziplistSafeToAdd(unsigned char* zl, size_t add) {
    size_t len = zl? ziplistBlobLen(zl): 0;
    if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
        return 0;
    return 1;
}

从上面的注释可以看到,ziplists不应该用于存储大的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值