深入浅出Redis-常见数据结构

文章目录

  • Redis常见数据结构
    • 前言
    • 数据结构
      • 动态字符串
        • 核心策略
        • 对比
      • 链表
        • 相关特性
      • 字典
        • 核心点
      • 跳跃表
      • 整数集合
      • 压缩列表
    • 对象
      • 字符串对象
      • 列表对象
      • 哈希对象
      • 集合对象
      • 有序集合对象
    • 其他
    • 参考资料

Redis常见数据结构

前言

  • 下面会根据redis源码看看我们常用的数据结构内部的实现原理。

数据结构

动态字符串

  • redis内部没有使用普通的C语言字符串,而是将其封装了一层,使得其不仅兼容C字符串还能自由动态变化, 我们看下内部数据结构:
struct sdshdr {
	// 当前字符串长度
    int len;
    // 可用空间
    int free;
    // 兼容C语言字符串,末尾'\0'结束
    char buf[];
};
核心策略
  • 空间预分配策略:
    1 如果需要增加的字符串长度小于free长度,那么不需要分配额外空间
    2 如果修改时free不够用并且修改后的字符串长度小于1MB,那么len和free长度保持相同,字符串长度为len+free+1其中1个字节为末尾的’\0’。
    2 如果修改后字符串长度大于等于30MB那么每次free长度动态扩大1MB,计算同上
  • 惰性空间释放:
    1 SDS的API提供了一套内存释放的功能,但是sds不会立刻释放空间,会先保留在free中使用,如果后期需要增长的话就立刻能派上用场。
对比
  • 最后我们讲一下redis使用sds结构而不是普通C字符串的几个原因:
序号 SDS C字符串
1 求长度的复杂度为O(1),性能提升 复杂度为O(n)
2 由于动态分配空间,不存在缓冲区溢出的可能 存在缓冲区溢出
3 二进制安全,不仅能保存文本数据还支持二进制数据 非二进制安全
4 修改N次字符串,内存重分配N次 修改N次字符串,内存分配最多N次

链表

  • c语言并没有内置链表这种结构,redis中自己设计了一种结构,在很多地方如: 发布订阅,慢查询,监视器等都用到了链表,redis服务器还用链表保存了多个客户端,所以链表在内部的使用必不可少,下面看下链表的相关数据结构:
/*
 * 双端链表结构
 */
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;

/*
 * 双端链表节点
 */
typedef struct listNode {

    // 前置节点
    struct listNode *prev;

    // 后置节点
    struct listNode *next;

    // 节点的值
    void *value;

} listNode;
相关特性
  1. 获取节点的头部和尾部的复杂度都是O(1)
  2. 不存在环,因为表头节点的prev和表尾节点的next都是NULL
  3. 能从任意一个节点发现前后节点的时间复杂度O(1)
  4. 链表长度获取复杂度O(1)
  5. 多态,结构中有dup,free,match三个属性为节点设置了特定类型函数, 所以redis链表可以保存不同的值.

字典

  • redis内部使用哈希表作为底层实现,我们由上到下看下数据结构:
/*
 * 字典
 */
typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */

} dict;

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
/*
 * 哈希表
 *
 * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
 */
typedef struct dictht {
    
    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;
    
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

    // 该哈希表已有节点的数量
    unsigned long used;

} dictht;

/*
 * 哈希表节点
 */
typedef struct dictEntry {
    
    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

/*
 * 字典类型特定函数
 */
typedef 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 *obj);

} dictType;
核心点
  • hash算法:计算不同的key存储的位置,计算伪代码如下:
// 算出key对应存储的hash
hashValue = dict->type->hasFunction(key);
// 通过hash和sizemask进行与操作,这里x可以是0或者1 看是否正在进行rehash
index = hashValue & ht[x].sizemask;
  • 渐进式rehash: 这一块有点复杂,简单来说就是保持负载因子在一个合适的范围内,维持程序的高可用性。具体可以参考这里

跳跃表

  • 首先看下数据结构
/* ZSETs use a specialized version of Skiplists */
/*
 * 跳跃表节点
 */
typedef struct zskiplistNode {

    // 成员对象
    robj *obj;

    // 分值
    double score;

    // 后退指针
    struct zskiplistNode *backward;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode;
  • 跳跃表是有序集合的底层实现之一
  • 具体细节略复杂,具体可以参考这里

整数集合

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

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

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

} intset;

压缩列表

对象

字符串对象

列表对象

哈希对象

集合对象

有序集合对象

其他

参考资料

[1] Redis设计与实现
[2] Redis官方网站
[3] Redis源码

你可能感兴趣的:(redis,数据结构,常见,底层数据结构,redis几种数据结构)