redis 数据结构与对象

1 动态字符串  SDS    (redis中字符串值、AOF缓冲区以及客户端输入缓冲区)

2 链表  linkedlist(发布与订阅、慢查询、监视器、redis本身保存多客户端的状态信息、构建客户端输出缓冲区)

3 字典 HashTable(又称为符号表、关联数组、映射、保存键值对抽象数据结构) 是hash键的底层

4 跳跃表  skiplist(有序集合键、集群节点中用作内部数据结构)

5 整数集合 Intset(集合键的底层实现)

6 压缩列表 ziplist(列表键和哈希键的底层实现)

7 对象

0字符串对象、列表对象、哈希对象、集合对象、有序集合对象

1 字符串对象(String)  int     raw     embstr(专门保存短字符串的一种优化编码方式)

2 列表对象(List)  ziplist  linkedlist

3 哈希对象(Hash) ziplist  hashtable

4 集合对象(Set)  intset  hashtable

5 有序集合对象(Zet)  ziplist  skiplist


1 动态字符串  SDS    (redis中字符串值、AOF缓冲区以及客户端输入缓冲区)

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

 

1 常数复杂度获取字符串长度

2 杜绝缓冲区溢出(api 会检查SDS是否满足需求)

3 减少字符串带来的内存重分配次数

(空间预分配、小于1MB  13+13+1      大于1MB  30MB+1MB+1) 惰性空间释放

4 二进制安全  使用len来判断字符串是否结束

5 兼容部分C字符串函数

 

 

2 链表  linkedlist(发布与订阅、慢查询、监视器、redis本身保存多客户端的状态信息、构建客户端输出缓冲区)

/*
 * 双端链表节点
 */
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;
```

 

 

3 字典 HashTable(又称为符号表、关联数组、映射、保存键值对抽象数据结构) 是hash键的底层

哈希表

hash算法采用MurmurHash算法

使用链地址法解决冲突

渐进式rehash

哈希表节点

字典

Ht[1]会在rehash的时候使用

哈希表的扩展与收缩

渐进性rehash

 

4 跳跃表  skiplist(有序集合键、集群节点中用作内部数据结构)

(平均O(logN))、最坏O(N)复杂度  效率可以和平衡树来媲美)

/*
 * 跳跃表
 */
typedef struct zskiplist {

    // 表头节点和表尾节点
    struct zskiplistNode *header, *tail;

    // 表中节点的数量
    unsigned long length;

    // 表中层数最大的节点的层数
    int level;

} zskiplist;

typedef struct zskiplistNode {

    // 成员对象
    robj *obj;

    // 分值
    double score;

    // 后退指针
    struct zskiplistNode *backward;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode;

 

 

跳跃表 skiplist 就是受到这种多层链表结构的启发而设计出来的。按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到 O(logn)

但是,这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的 2:1 的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点 (也包括新插入的节点) 重新进行调整,这会让时间复杂度重新蜕化成 O(n)。删除数据也有同样的问题。

skiplist 为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是 为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是 3,那么就把它链入到第 1 层到第 3 层这三层链表中。

 

 

 

说一下跳表的缺点:
1.内存占用比红黑树大(每个节点4指针);
2.由于插入时是随机选择levelcache友好性不够好;

https://blog.csdn.net/brillianteagle/article/details/52206261跳跃表原理与java实现

https://blog.csdn.net/DERRANTCM/article/details/79063312跳跃表Skip List的原理和实现

https://www.cnblogs.com/tong-yuan/p/skiplist.html 跳表的插入删除

5 整数集合 Intset(集合键的底层实现)

typedef struct intset {
    
    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;

6 压缩列表 ziplist(列表键和哈希键的底层实现)

`ziplist`本质上就是一个字节数组,是Redis为了节约内存而设计的一种线性数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数

连锁更新(cascade update),最坏情况下需要对压缩列表执行N次空间重分配操作,而每次重分配的最坏复杂度为 $O(N)$ ,所以连锁更新的最坏复杂度为$O(n^2)

7 对象

Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统

通过这五种不同的类型的对象,Redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

Redis的对象系统还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据键共享同一个对象来节约内存。

 

0字符串对象、列表对象、哈希对象、集合对象、有序集合对象

 

typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj;

 

1 字符串对象(String)  int     raw     embstr(专门保存短字符串的一种优化编码方式)

字符串对象的编码可以是int、raw或者embstr

 

- 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示

- 如果字符串对象保存的是一个字符串,并且这个字符串值的长度大于32个字节

- 如果字符串对象保存的是一个字符串,并且这个字符串值的长度小于等于32个字节(embstr);redisObject和sdshdr内存连续

 

 

 

2 列表对象(List)  ziplist  linkedlist

`ziplist`或者`linkedlist`

 

当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:

- 列表对象保存的所有字符串元素的长度都小于64字节

- 列表对象保存的元素数量小于512个

 

不能满足这两个条件的列表对象需要使用linkedlist编码

 

 

3 哈希对象(Hash) ziplist  hashtable

4 集合对象(Set)  intset  hashtable

`ziplist`或者`hashtable`

当哈希对象可以同时满足以下两个条件时,哈希对象使用`ziplist`编码:

- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个

不能满足这两个条件的哈希对象需要使用hashtable编码

5 有序集合对象(Zet)  ziplist  skiplist

ziplist`或者`skiplist`

```c
/*
 * 有序集合
 */
typedef struct zset {

    // 字典,键为成员,值为分值
    // 用于支持 O(1) 复杂度的按成员取分值操作
    dict *dict;

    // 跳跃表,按分值排序成员
    // 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
    // 以及范围操作
    zskiplist *zsl;

} zset;

当有序集合对象可以同时满足以下两个条件时,对象使用`ziplist`编码:

- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素成员的长度都小于64字节

不能满足以上两个条件的有序集合对象将使用skiplist编码

## 类型检查与命令多态

两种命令类型:对任**何类型**的键和**对特定类型的键**

###  类型检查的实现

通过redisObject结构的type属性来实现


### 多态命令的实现

Redis除了会根据值对象的类型来判断键是否能够执行命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令


### 内存回收

Redis在自己的对象系统中构建了一个引用计数技术实现内存回收机制

对象的整个生命周期可以划分为创建对象、操作对象、释放对象三个阶段

### 对象共享

Redis只对包含整数值的字符串对象进行共享

### 对象的空转时长

redisObject结构包含的最后一个属性为`lru`属性,该属性记录了对象最后一次**被命令**访问的时间