redis笔记-数据结构篇

发布时间:2020-06-22 17:34:44 作者:水天云黑白
来源:网络 阅读:1227
2018-1-3 by Atlas

1. SDS

redis底层是C语言编写,而redis没有直接使用C语言的字符串表示,是自己构建了一种名为简单动态字符串的抽象类型,即SDS(simple dynamic string)。
redis数据库里,包含字符串值的键值对在底层都是SDS实现的,可以说SDS是基石。
AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是SDS实现的。

struct sdshdr {
        // 记录buf数组中已使用字节的数量
        // 等于SDS所保存字符串的长度
        int len;
        // 记录buf数组中未使用字节的数量
        int free;
        // 字节数组,用于保存字符串
        char buf[];
}

redis笔记-数据结构篇

2. LIST

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
列表键的底层实现之一就是链表。
发布与订阅、慢查询、监视器等功能也用到了链表,redis服务器本身还使用链表来保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区。

typeof struct listNode {
        // 前置节点
        struct listNode *prev;
        // 后置节点
        struct listNode *next;
        // 节点的值
        void *value;
} listNode;
typeof struct list {
        // 表头节点
        listNode *head;
        // 表尾节点
        listNode *tail;
        // 链表包含的节点数量
        unsigned long len;
        // 节点值复制函数
        void *(*dup)(void *ptr);
        // 节点值释放函数
        void (*free)(void *ptr);
        // 节点值对比函数
        int (*match)(void *ptr,void *key);
} list;

redis笔记-数据结构篇

双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。
无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
带链表长度计数器:list结构的len属性使得获取链表节点数量的复杂度是O(1)。
多态:链表节点使用void *指针来保存节点值,并且可以通过list结构的dup、free、match属性为节点设置类型特定函数,所以链表可以保存各种不同类型的值。

3. HASH

字典是一种用于保存键值对的抽象数据结构。
字典中的键都是独一无二的。
redis数据库就是使用字典来作为底层实现的。
字典还是哈希键的底层实现之一。

typeof struct dictEntry {
        // 键
        void *key;
        // 值
        union{
                void *val;
                unit64_t u64;
                int64_t s64;
        } v;
        // 下个节点
        struct dictEntry *next;
} dictEntry;
typeof struct dictht {
        // 哈希表数组
        dictEntry **table;
        // 哈希表大小
        unsigned long size;
        // 哈希表大小掩码,用于计算索引值
        // 总是等于size-1
        unsigned long sizemask;
        // 哈希表已有节点的数量
        unsigned long used;
} dictht;
typeof 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 *val);
} dictType;
typeof struct dict {
        // 类型特定函数
        dictType *type;
        // 私有数据
        void *privadata;
        // 哈希表
        dictht ht[2];
        // rehash索引
        // 当不在进行rehash时,值为-1
        int trehashidx;
} dict;

redis笔记-数据结构篇

4. SKIPLIST

跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性来批量处理节点。
大部分情况下,跳跃表的效率和平衡树相媲美,并且实现较为简单。
redis使用跳跃表一个地方是作为有序集合键的底层实现之一,另一个地方是在集群节点中用作内部数据结构。

typeof struct zskiplistNode {
        // 后退指针
        struct zskiplistNode *backward;
        // 分值
        double score;
        // 成员对象
        robj *obj;
        // 层
        struct zskiplistLevel {
                // 前进指针
                struct zskiplistNode *forward;
                // 跨度
                unsigned int span;
        } level[];
} zskiplistNode;
typeof struct zskiplist {
        // 表头节点,表尾节点
        struct skiplistNode *header,*tail;
        // 表中节点数量
        unsigned long length;
        // 表中最大层数
        int level;
} zskiplist;

redis笔记-数据结构篇

redis笔记-数据结构篇

header指向跳跃表的表头节点。
tail指向跳跃表的表尾节点。
level记录跳跃表内表头外层数最大的节点的层数。
length记录跳跃表内表头外节点的数量。
层:前进指针用于访问位于表尾方向的节点,而跨度则记录前进指针指向节点和当前节点的距离。
后退指针指向位于当前节点的前一个节点,从表尾向表头遍历时使用。
各个节点按各自所保存的分值从小到大排列。

5. INTSET

整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,redis就会使用整数集合作为集合键的底层实现。
可以保存整数值类型有int16_t、int32_t、int64_t。

typeof struct intset {
        // 编码方式
        unit32_t encoding;
        // 集合包含的元素数量
        unit32_t lenght;
        // 保存元素的数组
        int8_t contents[];
} intset;

redis笔记-数据结构篇

INTSET_ENC_INT16表示整数集合的底层实现是int16_t类型的数组,集合保存的都是int16_t类型的整数值。
length属性值为5表示包含五个元素。
contents数组按从小到大属性保存集合的五个元素。
contents数组的大小等于16 * 5 = 80 位。

redis笔记-数据结构篇

INTSET_ENC_INT64表示整数集合的底层实现是int64_t类型的数组,集合保存的都是int64_t类型的整数值。
length属性值为4表示包含四个元素。
contents数组按从小到大属性保存集合的四个元素。
contents数组的大小等于64 * 4 = 256 位。

升级整数集合并添加新元素分三步:
1)根据新元素类型拓展整数集合底层数组的空间并为新元素分配空间。
2)将底层数组现有的所以元素都转换成与新元素相同的类型,并将类型转换后的元素放到正确的位上,需要维持底层数组的有序性质不变。
3)将新元素添加到底层数组。

6. ZIPLIST

压缩列表是列表键和哈希键的底层实现之一。
当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么是短字符串,那么redis会使用压缩列表来作为列表键的底层实现。
当一个哈希键只包含少量键值对,并且每个键值对要么是小整数值,要么是短字符串,那么redis会使用压缩列表来作为哈希键的底层实现。

一系列特殊编码的连续内存块组成的顺序型数据结构。可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。

redis笔记-数据结构篇

zlbytes记录整个压缩列表占用的内存字节数。
zltail记录压缩列表表尾节点距离压缩列表的起始地址有多少字节,通过整个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址。
zllen记录压缩列表的节点数量。
entryX是压缩列表的节点。
zlend用于标记压缩列表的末端。

压缩列表从表尾节点倒叙遍历,首先指针通过zltail偏移量指向表尾节点,然后通过指向节点记录的前一个节点的长度依次向前遍历访问整个压缩列表。

以上就是redis基本数据类型的底层数据结构。

参考文献:《redis设计与实现》

推荐阅读:
  1. 学习笔记之CSRF初级篇
  2. Redis理论篇-为什么使用Redis?

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

redis nosql 缓存

上一篇:如何用Canvas实现动态粒子连线特效

下一篇:go语言的学习路线

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》