Redis 探究底层存储结构

我们都知道Redis有五种数据类型,分别是字符串String,列表List,集合Sort,有序集合Sorted Set和散列表Hash,这些其实是Redis封装好的数据类型,Redis底层是用C语言编写的(大法好),所以用这边博客记录下这五种数据结构的底层是如何实现的。

字符串类型

C语言中字符串都是采用字符数组char[]来实现的,在Redis中是将字符数组封装成一个SDS结构体[Simple Dynamic String],SDS也是Redis的最小存储单元。

我们打开src目录下的sds.h文件

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

可以看到分别有8位,16位,32位和64位的sds,他们的内部结构大致都是相同的

  • len 记录char[] buf字符数组的长度
  • alloc 除去头部和终止符所占的长度
  • flags 三种类型 下面会详细说
  • buf[] 字符数组

然而仅仅使用封装的字符数组SDS并不能表示所有的五大类型,因此Redis在此之上继续进行封装,所以也就有了下面的RedisObject对象

在server.h中找到了redisObject结构体

#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

#define OBJ_SHARED_REFCOUNT INT_MAX
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits decreas time). */
    int refcount;
    void *ptr;
} robj;

可以看到封装的几个属性

  • type 表示redisObject是哪种类型
    • #define REDIS_STRING 0
    • #define REDIS_LIST 1
    • #define REDIS_SET 2
    • #define REDIS_ZSET 3
    • #define REDIS_HASH 4
  • *ptr 指针类型 指向的就是SDS类型

因此当我们运行下面的命令

127.0.0.1:6379> set name heqianqian
OK
127.0.0.1:6379> get name
"heqianqian"

存储了一个name=”heqianqian”的字符串,这里实际创建了两个redisObject对象,类型都是REDIS_STRING类型,底层的SDS保存的分别就是”name”和”heqianqian”两个字符串了。
一般键都是字符串类型,而值的话就可以有五种类型了

列表类型

Redis的List有点像双端队列,两头都可进可出

看一下adlist.h中是如何定义的

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;
  • listNode:有一个前驱节点和一个后继节点,还有一个value指针指向SDS类型
  • list
    • tail:存放尾节点
    • head:存放头节点
    • len:列表长度
      有这三个属性的话,操作表头表尾和统计长度的时间复杂度都可以是O(1)了

这里写图片描述

哈希类型

打开dict.h源码

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

哈希的底层结构,有五个属性

  • dictType
typedef struct dictType {
    uint64_t (*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;
  • dictht ht[2]
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

根据注释可以看到是用来表示哈希扩容的,至于这个属性为啥是个两个元素的数组,是因此哈希扩容可以有一次性扩容和渐进性扩容,所谓的渐进性扩容就是扩容的同时不影响前端的CURD,我慢慢的把数据从ht[0]转移到ht[1]中,同时rehashindex来记录转移的情况,当全部转移完成之后,将ht[1]改成ht[0]使用

dictht里也有四个属性

  • size:数组大小
  • sizemask:用来进行数组取模
  • used:记录已使用大小
  • dictEntry

dictEntry的结构如下

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

有三个属性
key对应哈希表中的键,value对应哈希表中的值。next指针就是使用链表解决哈希冲突

因此哈希结构大概如下所示

这里写图片描述

//TODO 待完善


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值