关于redis:redis源码之dict

34次阅读

共计 3322 个字符,预计需要花费 9 分钟才能阅读完成。

大家都晓得 redis 默认是 16 个 db,然而这些 db 底层的设计构造是什么样的呢?
咱们来简略的看一下源码, 重要的字段都有所正文

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB   字典数据结构,十分重要 */
    dict *expires;              /* Timeout of keys with a timeout set   过期工夫 */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)  list 一些数据结构中用到的阻塞 api*/
    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. */
} redisDb;

redis 中的所有 kv 都是寄存在 dict 中的,dict 类型在 redis 中十分重要。

字典 disc 的数据结构如下

typedef struct dict {
    dictType *type;  //
    void *privdata;  
    dictht ht[2];    //hashtable, 每个 dict 都有两个这样的数据结构,次要用于 hash 扩容
    long rehashidx; /* rehashing not in progress if rehashidx == -1  rehash 的作用  避免链表有限增长 */
    unsigned long iterators; /* number of iterators currently running   遍历记录的一些字段 */
} dict;

redis 中当呈现 hash 抵触的时候,咱们会采纳头插法 (链表) 的形式来解决,然而链表有限增常的话 hashtable 会进化,进化成一个链表,影响查问效率,这个时候咱们就须要对之前的数组进行扩容,把老的数据搬到新数组下面,这个过程就是 rehash

接下来咱们来看看 dictType 的类型

typedef struct dictType {uint64_t (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);  //key 用于数据类型的复制
    void *(*valDup)(void *privdata, const void *obj);  //value 用于数据类型的复制
    int (*keyCompare)(void *privdata, const void *key1, const void *key2); //hash 抵触的时候须要在抵触的值外面一个一个的比照
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;
typedef struct dictht {
    dictEntry **table;  // 指向数组的首地址  是健值对的外围构造
    unsigned long size;// 数组的长度
    unsigned long sizemask; // 恒等于 size-1
    unsigned long used;
} dictht; 
typedef struct  {
    void *key;  // 指向 SDS 的数据结构
    union {  // 联合体示意 value 类型, 只会用到一个字段
        void *val;    // 指向 redis 对象 redisObject
        uint64_t u64;
        int64_t s64;
        double d;  
    } v;
    struct dictEntry *next;  // 头插法解决 hash 抵触
} dictEntry;

接下来咱们看一下内存关系的对应图

typedef struct redisObject {
    unsigned type:4;  // 以后对象类型 list  string hash set zset 等
    unsigned encoding:4; //redis 做的底层优化(编码)
    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;
} robj;

最初咱们有一张总的图来表是 redis 的内存关系

encoding 存储的优化策略

1: 整型编码的解决

咱们先来看一个例子

127.0.0.1:6379> set type-int 12345
OK
127.0.0.1:6379> object encoding type-int
"int"
// 返回的 encoding 类型是 int
127.0.0.1:6379> set type-int-long 12345678901234567890
OK
127.0.0.1:6379> object encoding type-int-long
"embstr"
// 返回的 encoding 类型是 embstr

咱们能够发现,在都是数字的时候,如果长度小于 20,就会主动转换为 int 类型,这是 redis 中专门做的解决

if (len <= 20 && string2l(s, len, &value))

在一个 redisObject 中,就能够间接用 ptr 去存储整型值,而不必从新去开拓一块 sds 的空间

2:redis 对象字符串存储相干优化
127.0.0.1:6379> set type-str-short xxx
OK
127.0.0.1:6379> object encoding type-str-short
"embstr"




127.0.0.1:6379> set type-str-long xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-x
// 字符串长度 45
127.0.0.1:6379> object encoding type-str-long
"raw"



127.0.0.1:6379> set type-str-long2 xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-
// 字符串长度 44
127.0.0.1:6379> object encoding type-str-long2
"embstr"

一个 redisobject 是存在内存中的,cpu 在实现一个 io 的时候,它是怎么来读数据的呢,其实 cup 的 io 中有一个缓冲行的概念,在 linux 零碎中,一个缓冲行个别是 64 个字节
接下来咱们看看一个 redis 对象大略占多大的内存空间,其实咱们能够大略算进去。

typedef struct redisObject {
    unsigned type:4;  //4bit
    unsigned encoding:4; //4bit
    unsigned lru:LRU_BITS; //24bit
    int refcount;   //4byte
    void *ptr;   //8byte
} robj;

一个 redis 对象自身就须要占(4bit+4bit+24bit = 4byte)+ 4byte + 8byte = 16byte 的大小

这样的话一个缓冲行还残余 48 个 byte 的大小,有点节约,
48 个 byte,依照 sds 的调配策略应该在 sdshdr8 那个区间中,而 sdshdr8 自身就须要占 3 个字节,sds 须要兼容 c 语言的函数库,都会在结尾加上 0,所以 sdshdr8 自身是占用 4 个字节,所以一个缓冲行中还残余 44 个字节,来存储残余的数据,所以在 redis 字符串对象中,当长度小于 44 的时候,encoding 的类型是 embstr,没有新开拓一块 sds 空间

关注我的技术公众号,每周都有优质技术文章推送。
微信扫一扫下方二维码即可关注:

正文完
 0