Redis 探究底层存储结构

来源:互联网 发布:网店的营销策略数据 编辑:程序博客网 时间:2024/05/21 19:29

我们都知道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_MAXtypedef 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 heqianqianOK127.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 待完善