数据库
首页 > 数据库> > 面试题】Redis常用数据类型,以及底层实现

面试题】Redis常用数据类型,以及底层实现

作者:互联网

一、Redis的常用数据类型

字符串(string),队列(list),哈希(hash),集合(sets),有序集合(sorted sets)。

二、底层实现

1、字符串

Redis中的字符串都是由动态字符串(simple dynamic string SDS)实现的

SDS数据结构定义:

struct sdshdr {
  int len;// buf 中已占用字节数
  int free;// buf 中剩余可用字节数
  char buf[];// 数据空间
};

Redis 3.2之前的SDS这样设计的有点:

1)有单独的统计变量len和free(称为头部)。可以很方便地得到字符串长度。
2)内容存放在柔性数组buf中,SDS对上层暴露的指针不是指向结构体SDS的指针,而是直接指向柔性数组buf的指针。上层可像读取C字符串一样读取SDS的内容,兼容C语言处理字符串的各种函数。
3)由于有长度统计变量len的存在,读写字符串时不依赖“\0”终止符,保证了二进制安全。

2、队列(list)

list的底层数据结构时链表。Redis 3.2之前的版本(包含3.2)使用的时ziplist或者linkedlist,Redis 3.2之后版本使用的quicklist。

ziplist

压缩列表是为了节约内存而开发的,是存储在连续内存块上的顺序数据结构。一个压缩列表可以包含任意多的 entry 节点,每个节点包含一个字节数组或整数。redis 中并没有显式定义 ziplist 的数据结构,仅仅提供了一个描述结构 zlentry 用于操作数据。

typedef struct zlentry {
    unsigned int prevrawlensize;// 用于记录前一个 entry 长度的字节数
    unsigned int prevrawlen;    // 前一个 entry 的长度
    unsigned int lensize        // 用于记录当前 entry 类型/长度的字节数
    unsigned int len;           // 实际用于存储数据的字节数
    unsigned int headersize;    // prevrawlensize + lensize
    unsigned char encoding;     // 用于指示 entry 数据的实际编码类型
    unsigned char *p;           // 指向 entry 的开头
} zlentry;

各字段含义如下:

该数据结构具有以下特征:

quicklist

在较早版本的 redis 中,list 有两种底层实现:

两者各有优缺点:

为了结合两者的优点,在 redis 3.2 之后,list 的底层实现变为快速列表 quicklist。
快速列表是 linkedlist 与 ziplist 的结合: quicklist 包含多个内存不连续的节点,但每个节点本身就是一个 ziplist。

typedef struct quicklistNode {
    struct quicklistNode *prev;  // 上一个 ziplist 
    struct quicklistNode *next;  // 下一个 ziplist 
    unsigned char *zl;           // 数据指针,指向 ziplist 结构,或者 quicklistLZF 结构
    unsigned int sz;             // ziplist 占用内存长度(未压缩)
    unsigned int count : 16;     // ziplist 记录数量
    unsigned int encoding : 2;   // 编码方式,1 表示 ziplist ,2 表示 quicklistLZF
    unsigned int container : 2;  // 
    unsigned int recompress : 1;         // 临时解压,1 表示该节点临时解压用于访问
    unsigned int attempted_compress : 1; // 测试字段
    unsigned int extra : 10;             // 预留空间
} quicklistNode;

typedef struct quicklistLZF {
    unsigned int sz;    // 压缩数据长度
    char compressed[];  // 压缩数据
} quicklistLZF;

typedef struct quicklist {
    quicklistNode *head;  // 列表头部
    quicklistNode *tail;  // 列表尾部
    unsigned long count;  // 记录总数
    unsigned long len;    // ziplist 数量
    int fill : 16; // ziplist 长度限制,每个 ziplist 节点的长度(记录数量/内存占用)不能超过这个值
    unsigned int compress : 16; // 压缩深度,表示 quicklist 两端不压缩的 ziplist 节点的个数,为 0 表示所有 ziplist 节点都不压缩
} quicklist;

该数据结构有以下特征:

3、哈希(hash)

hash 类型是 Redis 常用数据类型之一,其底层存储结构有两种实现方式。

当存储的数据量较少的时,hash 采用 ziplist 作为底层存储结构,此时要求符合以下两个条件:

当无法满足上述条件时,hash 就会采用 dict(字典结构)来存储数据,数据结构如下:

typedef struct dictEntry {
    void* key;  // 键
    union {     // 值,可以为指针、有符号长整,无符号长整,双精度浮点
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct dictht {
    dictEntry **table;      // 哈希表数组,数组中的每个元素是一个单向链表
    unsigned long size;     // 哈希表数组大小
    unsigned long sizemask; // 哈希掩码,用于计算索引
    unsigned long used;     // 已有节点数量
} dictht;

typedef struct dictType {
    unsigned int (*hashFunction) (const void *key);             // 哈希函数,用于计算哈希值
    int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 键比较函数
    void *(*keyDup)(void *privdata, const void *key);           // 键复制函数
    void *(*valDup)(void *privdata, const void *obj);           // 值复制函数
    void *(*keyDestructor)(void *privdata, const void *key);    // 键销毁函数
    void *(*valDestructor)(void *privdata, const void *obj);    // 值销毁函数
} dictType;

typedef struct dict {
    dictType *type;     // 类型函数,用于实现多态
    void *privdata;     // 私有数据,用于实现多态
    dictht ht[2];       // 哈希表,字典使用 ht[0] 作为哈希表,ht[1] 用于进行 rehash
    int rehashidx;      // rehash索引,当没有执行 rehash 时,其值为 -1
} dict;

数据结构有以下特征:

4、集合(sets)

Redis set 采用了两种方式相结合的底层存储结构,分别是 intset(整型数组)与 hash table(哈希表),当 set 存储的数据满足以下要求时,使用 intset 结构:

当不满足上述要求时,则使用 hash table 结构。

intset 的结构体定义如下:

typedf struct inset{
    uint32_t encoding;//指定编码方式,默认为INSET_ENC_INT16
    uint32_t length;//集合内成员的总个数
    int8_t contents[];//实际存储成员的数组,并且数组中的数值从小到大依次排列
}inset;

有序整型集合,具有紧凑的存储空间,添加操作的时间复杂度为O(N)

5、有序集合(sorted sets)

有序集合(zset)同样使用了两种不同的存储结构,分别是 zipList(压缩列表)和 skipList(跳跃列表),当 zset 满足以下条件时使用压缩列表:

当有序结合不满足使用压缩列表的条件时,就会使用 skipList 结构来存储数据。

跳跃列表(skipList)又称“跳表”是一种基于链表实现的随机化数据结构,其插入、删除、查找的时间复杂度均为 O(logN)。从名字可以看出“跳跃列表”,并不同于一般的普通链表,它的结构较为复杂,本节只对它做浅显的介绍,如有兴趣可自行研究。

在 Redis 中一个 skipList 节点最高可以达到 64 层,一个“跳表”中最多可以存储 2^64 个元素,每个节点都是一个 skiplistNode(跳表节点)。skipList 的结构体定义如下:

typedf struct zskiplist{
    //头节点
    struct zskiplistNode *header;
    //尾节点
    struct zskiplistNode *tail;
    // 跳表中的元素个数
    unsigned long length;
    //表内节点的最大层数
    int level;
}zskiplist;

跳跃列表的每一层都是一个有序的链表,链表中每个节点都包含两个指针,一个指向同一层的下了一个节点,另一个指向下一层的同一个节点。最低层的链表将包含 zset 中的所有元素。如果说一个元素出现在了某一层,那么低于该层的所有层都将包含这个元素,也就说高层是底层的子集。

该数据结构有以下特征:

标签:面试题,int,数据类型,ziplist,Redis,unsigned,列表,void,节点
来源: https://www.cnblogs.com/wsx2019/p/15738349.html