一、redis底层数据结构

一、SDS(simple Dynamic String)

简单动态字符串

结构图:
在这里插入图片描述

三个属性:

len: 保存字符串的字节长度
free: 字节数组未使用的空间大小
buffer:字节数组,保存二进制数据。最后一个总是'\0',保持C字符串的惯例,复用部分C字符串的函数库。

新版本源码 (sds.h)

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
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[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

特点:
1、相比旧版本,每个字符串无论多长,都要占用8个字节的长度(free和len),新版本根据长度选择合适的sdshdr,节约空间。
2、C语言中字符串,没有记录字符串长度的属性,获取字符串长度,就要从头遍历到末尾,复杂度O(n);
3、SDS的len记录了字符串长度,复杂度O(1)。
4、C语言字符串拼接,可能造成缓冲区溢出(提前未分配足够的空间)。SDS会提前计算分配空间,不够的话,会自动扩展。
5、减少修改字符串时带来的内存重分配次数,C字符串底层关联着数组,修改字符串会带来内存的重新分配。

SDS的空间预分配:
1、len小于1M时,分配free的大小等于len,相当两倍的扩容
2、大于等于1M时,每次增加1M的未使用空间

SDS的惰性释放:
字符串缩减时,不会内存重分配,而是将释放的空间记录加在free上,为下次的字符串追加做准备。

二、链表

源码(adlist.h)

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;

list持有更方便

结构图:
在这里插入图片描述

特点:
1、获取头节点和尾节点的复杂度是O(1)
2、获取链表的长度也是O(1)
3、多态,使用指针保存值,支持多种类型的值

广泛用在列表键,发布订阅,监视器,慢查询等。

三、字典

字典又称符号表,关联数组,映射(map),存储键值对的一种数据结构抽象。

数据库底层的存储结构,hash键的底层实现之一(键值对比较多或者元素比较长)。

Redis字典使用哈希表实现,一个哈希表包含多个哈希表节点,每个节点存储字典的一个键值对。

结构图:
在这里插入图片描述
源码(dict.h)

struct dict {
    dictType *type; //键值对类型,为创建多态字典设计

    dictEntry **ht_table[2]; //两张hash表
    unsigned long ht_used[2]; //两张哈希表中,分别使用了多少

    long rehashidx; 
	int16_t pauserehash;
    signed char ht_size_exp[2];

   
};
ht_table[2] //两张哈希表,一般情况只使用ht_table[0],ht_table[1]在 rehash时使用,rehashidx是-1,则代表没有进行rehash。

typedef struct dictEntry {
    void *key; //键
    union {
        void *val; //值可以是指针
        uint64_t u64; //值是uint64_t
        int64_t s64; //值是int64_t
        double d;  //值是double
    } v;
    struct dictEntry *next;   //下一个节点,用次解决hash冲突,采用头插法
    void *metadata[];         
                              
                              
} dictEntry;


typedef struct dictType {
    uint64_t (*hashFunction)(const void *key); //计算hash 函数值
    void *(*keyDup)(dict *d, const void *key);  //复制键
    void *(*valDup)(dict *d, const void *obj);  //复制值
    int (*keyCompare)(dict *d, const void *key1, const void *key2); //对比键
    void (*keyDestructor)(dict *d, void *key); //销毁键
    void (*valDestructor)(dict *d, void *obj);  //销毁值
    int (*expandAllowed)(size_t moreMem, double usedRatio);
    size_t (*dictEntryMetadataBytes)(dict *d);
} dictType;


rehash过程:
1、如果是扩容则扩到第一个大于等于 ht_table[0]使用空间的2倍且是 2的n次幂。
2、如果是收缩,则是 第一个大于等于 ht_table[0]使用空间且是 2的n次幂。
3、将ht_table[0]上的建rehash到ht_table[1]上,全部rehash到ht_table[1]后,则释放ht_table[0],将ht_table[1]设为ht_table[0],在ht_table[1]上创建空白的哈希表,为下一次的rehash做准备。

哈希表扩容和收缩的条件
1、服务器没有执行bgsave 或bgrewriteaof,且负载因子大于等于1
2、服务器正在执行bgsave 或bgrewriteaof,且负载因子大于等于5
3、负载因子小于0.1时,执行哈希表的收缩

负载因子 = ht_used[0] / ht_table[0]

渐进式rehash步骤:
1、为 ht_table[1]分配空间,让字典同事持有 ht_table[0]和 ht_table[1]
2、字典维持一个索引计数器rehashidx,值设为0,标识rehash正式开始
3、在rehash期间,字典执行增删改查时,程序除了执行操作,还要将ht_table[0]在rehashidx上的所有键值对迁移到ht_table[1]上,完成后rehashidx加1
4、随着字典的不断操作,所有的ht_table[0]上的键值对都会被rehash到ht_table[1]上,完成迁移,rehashidx 设为-1

注意:
渐进式rehash过程,如果查询字典,则会分别查询ht_table[0]和 ht_table[1],新增时,则会增加到ht_table[1]上,保证ht_table[0]只减不增。

四、跳跃表

跳跃表是一个有序的数据结构,通过在每个节点维持多个指向其他节点的指针,从而达到快速访问的目的。
复杂度平均O(logN)最坏O(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 zskiplist {
    struct zskiplistNode *header, *tail;  //头尾节点
    unsigned long length; //节点数量
    int level; //一共多少层数
} zskiplist;

层数是1至32的随机数
节点按照分值从小到大排序,分值相同按照成员排序。

五、整数集合

集合键的底层实现之一(集合只包含整数元素,并且元素数量不多)

源码(intset.h):

typedef struct intset {
    uint32_t encoding;  //编码方式
    uint32_t length;    //元素数量
    int8_t contents[];  //保存元素的数组,类型取决于encoding属性,从小到大排序
} intset;节点

结构图:
在这里插入图片描述

contents[]升级,当数组中都是int16_t,突然加入一个int64_t,则会将所有的元素都升级为int64_t。

只支持升级,不支持降级。

升级的好处:提升灵活性,节约内存。

升级过程:
1、扩展整数集合底层数组的空间大小
2、将原元素转换成新元素类型,从后往前迁移,放在合适的位置
3、将新元素添加到合适位置

六、压缩列表

列表键(列表项比较少或者列表项是小整数或者短字符串)和哈希建的底层实现之一。

结构图:
在这里插入图片描述

压缩列表组成部分
zlbytes: 占用的字节数
zltail:记录压缩列表的尾节点距离到压缩列表的起始地址有多少字节
z1len: 节点数量,大于65535时,只能遍历节点才能知道数量
entryn: 压缩列表节点,字节数组或者一个整数值
zlend:特殊0xFF,标记压缩列表的末端

每个压缩列表节点:
previous_entry_length : 前一个节点的字节长度,使用一个字节或者5个字节存储,用它可以回溯到压缩列表的头部
encoding :记录content属性的类型和长度
content :节点的值
在这里插入图片描述
连锁更新:
增加一个大于254长度的节点,为了让previous_entry_length满足压缩列表对节点的要求,从头到尾的更新空间大小,执行空间重分配。

删除节点也可能引发连锁更新。

恰好有多个,连续的,长度介于250到253之间的节点才会引起连锁更新,连锁更新的节点不多,不会影响性能。

七、quicklist

quicklist是由ziplist组成的双向链表,链表中的每一个节点都以压缩列表ziplist的结构保存着数据,而ziplist有多个entry节点,保存着数据。相当与一个quicklist节点保存的是一片数据,而不再是一个数据。

typedef struct quicklist {
    quicklistNode *head; //头节点
    quicklistNode *tail; //尾节点
    unsigned long count; //ziplist节点计数器
    unsigned long len;   //quicklist的quicklistNode节点计数器
    signed int fill : QL_FILL_BITS; //保存ziplist的大小,配置文件设定,占16bit
    unsigned int compress : QL_COMP_BITS;  //保存压缩程度值,配置文件设定,占16bits,0表示不压缩
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

typedef struct quicklistNode {
    struct quicklistNode *prev; //前驱节点
    struct quicklistNode *next; //后继节点
    unsigned char *entry;  //不设置压缩数据参数recompress时指向一个ziplist结构
						   //设置压缩数据参数recompress指向quicklistLZF结构
    size_t sz;         //压缩列表ziplist的总长度   
    unsigned int count : 16;     //ziplist中包的节点数,占16 bits长度
    unsigned int encoding : 2;  //表示是否采用了LZF压缩算法压缩quicklist节点,1表示压缩过,2表示没压缩,占2 bits长度
    unsigned int container : 2; //表示一个quicklistNode节点是否采用ziplist结构保存数据,2表示压缩了,1表示没压缩,默认是2,占2bits长度
    unsigned int recompress : 1; //如果recompress为1,则等待被再次压缩
    unsigned int attempted_compress : 1; 
    unsigned int extra : 10; 
} quicklistNode;
typedef struct quicklistLZF {
    size_t sz; //表示被LZF算法压缩后的ziplist的大小
    char compressed[]; //保存压缩后的ziplist的数组,柔性数组
} quicklistLZF;
//管理quicklist中quicklistNode节点中ziplist信息的结构
typedef struct quicklistEntry {
    const quicklist *quicklist;   //指向所属的quicklist的指针
    quicklistNode *node;          //指向所属的quicklistNode节点的指针
    unsigned char *zi;            //指向当前ziplist结构的指针
    unsigned char *value;         //指向当前ziplist结构的字符串vlaue成员
    long long longval;            //指向当前ziplist结构的整数value成员
    unsigned int sz;              //保存当前ziplist结构的字节数大小
    int offset;                   //保存相对ziplist的偏移量
} quicklistEntry;

结构图:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值