快速复习Redis数据结构和对象

137 阅读3分钟

今天下午用一个小时复习了下Redis数据结构和对象,现在记录一下

如有表述不清或错误请斧正

简单动态字符串

在C字符串的基础上做了封装,底层是char类型的数组

SDS

struct sdshdr{
    int len;     // 字符串的长度
    int free;    // 空闲长度
    char buf[];  // 字符数组
}

特点

  • O(1)获取字符串长度

  • 杜绝缓冲区溢出的风险(free属性记录空闲的可用空间数)

  • 减少内存分配和回收的次数

  • 二进制安全(因为不以\0判断结尾)

链表

其实就是双向无环链表

链表节点

typedef struct listNode{
    listNode *prev;
    listNode *next;
    void *value;
}list;

链表

typedef struct list{
    listNode *head;
    listNode *tail;
    unsigned long len;
    void *(*dup)(void *ptr);
    void *(*free)(void *ptr);
    void *(*match)(void *ptr, void *key);
}list;

字典

字典 -> 哈希表 -> 哈希表节点

字典

typedef struct dict{
    dictType *type;    // dictType结构体含有一些函数指针,用于创建多态字典
    void *privatedata; // 上述函数的参数
    dictht ht[2];      // 平时只使用一个,rehash时使用另外一个
    int rehashidx;     // rehash索引
}dict;

哈希表

typedef struct dictht{
    dictEntry **table;       // 数组结构
    unsigned long size;      // 数组大小
    unsigned long sizemask;  
    unsigned long used;      // 哈希表含有的键值对数量
}dictht;

哈希表节点

typedef struct dictEntry{
    void *key;       // 键
    union{
        void *val;   // 值
        uint64_tu64;
        int64_ts64;
    }
    dictEntry *next; // 拉链法处理哈希冲突,头插法
}dictEntry;

rehash

何时?

  • 负载因子 = used / size
  • 没有BGSAVE,且,没有BGREWRITEAOF,负载因子 > 1 时
  • 在BGSAVE,或,在BGREWRITEAOF,负载因子 > 5 时
  • 负载因子 < 0.1 时
  • 扩容或收缩到2的n次方

渐进式

  • rehash到ht[1]
  • 使用rehashidx记录rehash进度,开始时为0,结束时为-1
  • 每次对字典的增删改查都会rehash当前rehashidx上的所有记录,并rehashidx++
  • 分配,rehash,指向,释放
  • 新哈希表ht[1]只增不减
  • 所有操作会在原和新哈希表上发生

跳跃表

是顺序的数据结构,在链表上加索引来增加查找的效率,实现简单

跳跃表

typedef struct zskiplist { 
    struct zskiplistNode *header, *tail; // 首尾节点
    unsigned long length;                // 长度
    int level;                           // 最大的level值
} zskiplist;

跳跃表节点

typedef struct zskiplistNode { 
    struct zskiplistLevel{
        struct zskiplistNode *forward;        // 前进指针
        usigned int span;                     // 跨度
    }level[];                             // 层数组
    struct zskiplistNode *backword;       // 后退指针
    double score;                         // 分值
    sds ele;                            // 成员对象,指向一个字符串对象,SDS
} zskiplistNode; 

注意

  • score可以相同
  • 成员对象必须要唯一
  • score相同时按字典序排
  • 跳表的相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)
  • 新插入的节点的层高是随机的

压缩列表

顺序内存,适用于较短的字符串和较少的整数值

image.png

压缩列表

  • zlbytes
  • zltail
  • zllen
  • entry * N
  • zlend

压缩列表节点

  • previous_entry_length
  • encoding
  • content

连锁更新

可能连锁更新,不过对性能的影响可以忽略

整数集合

本质是数组,顺序内存

整数集合

typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

升级

  • 只能升级,不能降级
  • 新到的元素大于现有编码的数据类型所能容纳的范围时发生
  • 新到的元素总是在升级后的第一个/最后一个

quicklist

可以理解为「双向链表 + 压缩列表」

一个quicklist是一个双向链表,链表的每一个节点时一个压缩列表

image.png

在向 quicklist 添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到 quicklistNode 结构里的压缩列表,如果不能容纳,才会新建一个新的 quicklistNode 结构

listpack

image.png