Bootstrap

Redis底层设计与源码分析---学习笔记

一.Redis为什么快

高速的存储介质

在这里插入图片描述
机械硬盘—>固态硬盘—>内存,随机访问的延迟逐渐变小

优良的底层数据结构设计

底层设计用到了hashtable,时间复杂度低
在这里插入图片描述

高效的网络IO模型

epoll等,不同平台有不同的实现

高效的线程模型

二.Redis的HashTable

Redis和编码介绍

hash值的计算公式:

hash(key) % hashtable.size() 取模得到一个index索引,然后把entry存进去


三.Redis的渐进式ReHash

当HashTable的某个key中链表的节点个数,大于HashTable的size,就会触发扩容。

扩容的时候,因为不能卡顿,所以不会一次性把所有hash槽的内容都挪到新的空间,而是渐进地搬运。redis会维护两个HashTable,首先访问老的,如果老的没有会去新的HashTable访问,有新的元素加进来,放到新的HashTable

四.Redis的Key类型是什么

可以是任意的数据类型,不管传什么类型,都会转成redis的string字符串类型

五.Redis的String

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[];
};
 	

首先,根据数据长度的不同,有很多种sdshdr的实现,从5一直到64,就是bit位数量有区别。

而且,每次扩容的时候,假设size从6变成7,那么申请2x7=14的空间,


六.Redis的数据库设计

/*  */
typedef struct redisDb {
    dict *dict;                 /* db的keyspace也就是kv对的结构 */
    dict *expires;              /* 过期时间 */
    dict *blocking_keys;        /* 阻塞的API (例如BLPOP)*/
    dict *blocking_keys_unblock_on_nokey;   /* Keys with clients waiting for
                                             * data, and should be unblocked if key is deleted (XREADEDGROUP).
                                             * This is a subset of blocking_keys*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
    clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;

然后看一下关键的字典对象dict

struct dict {
    dictType *type; //字典类型

    dictEntry **ht_table[2]; //ht就是hashTable, 这里有两个一个是新的一个是老的
    unsigned long ht_used[2];

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */

    /* Keep small vars at end for optimal (minimal) struct padding */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
    signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */

    void *metadata[];          
};

下面看一下dictType

typedef struct dictType {
    uint64_t (*hashFunction)(const void *key); //hash函数
    void *(*keyDup)(dict *d, const void *key);
    void *(*valDup)(dict *d, const void *obj);
    int (*keyCompare)(dict *d, const void *key1, const void *key2);
    void (*keyDestructor)(dict *d, void *key);
    void (*valDestructor)(dict *d, void *obj);
    int (*expandAllowed)(size_t moreMem, double usedRatio);
    //其他省略
} dictType;

下面是redisObject的结构,也是键值对的值,它会封装数据结构(string,hash,ziplist等)还有编码结构

struct redisObject {
    unsigned type:4;
    unsigned encoding:4; //编码,raw,int,embstr等
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;//指针,指向实际的地址
};

七.Redis的String

每个redis命令都是一个redisCommand。

字符串在set的时候,如果长度小于20,可能会转成int类型

八.Redis的emb str

44是一个临界点:

  1. 如果字符串长度小于等于44,那么是一个emb str类型
  2. 如果大于44,那么是一个raw类型
  3. 有4个byte的元数据要存储,加起来是48个字节

缓存行cacheline是64个字节,64 = 48 + 16,redis进行了优化,减少内存IO。

九.Redis的list

List是一个有序的数据结构,底层是quickList双端列表和zipList作为底层的实现。

zipLIst
/* 创建一个空的 ziplist. */
unsigned char *ziplistNew(void) {
    unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
    unsigned char *zl = zmalloc(bytes);
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
    ZIPLIST_LENGTH(zl) = 0;
    zl[bytes-1] = ZIP_END;
    return zl;
}

redis没有直接用双端链表,而是用zipList,因为两个指针占的空间太多了,而zipList是存储在连续内存上的
可以参考:redis的ziplist

quickLIst

quicklist讲解

在这里插入图片描述

  1. Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist。quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
  2. quicklist有关的数据结构定义在quicklist.h中。

十.Redis的Set

redis的set底层是一个hashTable,只不过value是null(这个和jdk一样的),假如set的元素都是整形,redis会自动用intset这种数据类型。

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

十一.Redis的Hash

hash也是用zipList来存储的,如果元素很多,那么就用hashtable。ziplist是内存紧凑的数据结构,缺点是如果老加元素,就要频繁分配内存空间

十二.Zset的数据结构

相比于set,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。

zset有两种不同的实现,分别是zipList和skipList。

zipList

满足以下两个条件:

[score,value]键值对数量少于128个;
每个元素的长度小于64字节;

skipList:

不满足以上两个条件时使用跳表(组合了hash和skipList)

hash用来存储value到score的映射,这样就可以在O(1)时间内找到value对应的分数;
skipList按照从小到大的顺序存储分数;
skipList每个元素的值都是[score,value]对

;