redis简介(三) redis中的存储数据结构

redis是一种基于key-value的内存数据库。所谓kv存储或者说kv数据库,就是说里面数据都是一对一对的存储的,其中key是唯一的一个索引。这种结构一般是基于哈希表,效率极高,查找复杂度为O(1)。

不过hashmap也不是万能的,随着数据量的增多,造成的hash冲突也就越严重,复杂度增加,redis需要进行rehash,对hashmap进行扩容。如图所示,redis还支持丰富的数据结构类型,那么这些是怎么做到的呢?它们在内存中是怎么存储的呢?

 

1. redis中的对象

在redis中每一个对象都对应一个redisObject对象,如下,这里重点讲type、encoding和ptr

127.0.0.1:6379> set key "hello world"
OK
127.0.0.1:6379> get key
"hello world"
127.0.0.1:6379> type key
string
127.0.0.1:6379> RPUSH scores 87 89 97
(integer) 3
127.0.0.1:6379> type scores
list

 

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 access time). */
    int refcount;
    void *ptr;
} robj;

1.1 类型

对象的类型有以下五种,分别代表字符串、列表、集合、有序集合和哈希对象。

 

/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

一个数据库键为什么类型,可以通过TYPE命令查看

 

127.0.0.1:6379> set key "hello world"
OK
127.0.0.1:6379> get key
"hello world"
127.0.0.1:6379> type key
string
127.0.0.1:6379> RPUSH scores 87 89 97
(integer) 3
127.0.0.1:6379> type scores
list

1.2 编码

对象的ptr指针指向对象的底层数据结构,对应encoding属性。如下

 

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

相应的查看命令 object encoding key , 这些编码并非已成不边,比如int编码和embstr编码的字符串对象,有可能会变为raw编码的字符串对象

127.0.0.1:6379> set hi "hello"
OK
127.0.0.1:6379> object encoding hi
"embstr"
127.0.0.1:6379> sadd numbers 87 69 90
(integer) 3
127.0.0.1:6379> object encoding numbes
(nil)
127.0.0.1:6379> object encoding numbers
"intset"

1.3 对象指针

如下图所示,为RAW编码的字符串对象

 

 

下图为ziplist编码的列表对象,同理其他编码也都是用这种结构

1.4 引用计数实现内存回收

redis的对象系统中使用引用计数来实现内存回收。当引用计数为0时,释放对象回收内存。

1.5 lru

lru这个属性表示对象最后一次被访问的时间到当前时间的间隔。相应命令如下

 

127.0.0.1:6379> OBJECT idletime key
(integer) 1942

 

如果设置了maxmemory选项,回收算法为volatile-lru或者allkeys-lru的话,当key数量超出上限,lru时长高的那部分将会被回收。

 

2. 字典

Redis中采用了字典这一结构来管理键值对,用它作为数据库的底层实现,同样也是哈希键的底层实现。下面从哈希表、哈希表节点和字典的三个重要的数据结构分来分析下。

2.1 哈希表

 

/* 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;

table: 哈希表数组,size:哈希表数组大小,sizemask:哈希表大小掩码,用来确定新键的位置,used:哈希表已有节点的数量。

 

2.2 哈希表节点

 

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

key保存键,v保存值,next指向另一个节点,用来解决键冲突。可以看到v是一个联合体,对应的值可以是uint64_t\int64_t\double这些基础类型,也可以是指针,间接指向对象。

 

2.3 字典

 

typedef struct dictType {
    uint64_t (*hashFunction)(const void *key);// 计算hash值的函数
    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;

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;

type是一个dictType的指针。dictType中保存了一组用操作键值对的函数,privdata则是这些函数的可选参数。这类似于C++中的虚函数表指针(更确切的讲是虚表指针类似于它),以此来实现多态。

ht是一个包含两个dictht的数组,字典正常情况下只使用ht[0], ht[1]只会在rehash时使用, rehashidx则记录了目前的进度,没有rehash,则默认为-1。下图为普通状态下的字典、hash表和哈希表节点。

 

 

参考:

[0] https://hazelcast.com/glossary/key-value-store/

[1] redis设计与实现

[2] http://www.redis.cn/documentation.html

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值