Redis存储键值方式详解

redis是一个存储键值对的内存数据库,其存储键值的方式和Java中的HashMap相似。表征redis数据库的结构体是redisDb (在server.h文件中),redis服务器默认有16个数据库,编号从0到15。

typedef struct redisDb {
    dict *dict;                /* 键空间 */
    dict *expires;              /* 过期键空间 */
    dict *blocking_keys;        /* 客户端在等待的键 (BLPOP) */
    dict *ready_keys;          /* 接收到 push 的阻塞键 */
    dict *watched_keys;        /* WATCHED keys for MULTI/EXEC CAS */
    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
    int id;                    /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
} redisDb;

dict 中存储的是 key -> value,而expires存储的 key -> 过期时间

dict是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;

typedef struct dictht {
    dictEntry **table;
    unsigned long size; //table的大小
    unsigned long sizemask;
    unsigned long used; //table中键值对的数量
} dictht;

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

dict可以类比为java中的HashMap,dictEntry对应java.util.HashMap.Entry<K, V>,稍微不同的是,dict对entry的table做了简单的封装(即dictht),而且dict中有两个table用于rehash。

分析dict的dictReplace(dict.c文件中),类似于HashMap的put:

/* Add or Overwrite:
 * Add an element, discarding the old value if the key already exists.
 * Return 1 if the key was added from scratch, 0 if there was already an
 * element with such key and dictReplace() just performed a value update
 * operation. */
int dictReplace(dict *d, void *key, void *val)
{
    dictEntry *entry, *existing, auxentry;

/* Try to add the element. If the key
    * does not exists dictAdd will suceed. */
    entry = dictAddRaw(d,key,&existing);
    if (entry) {
        dictSetVal(d, entry, val);
        return 1;
    }

/* Set the new value and free the old one. Note that it is important
    * to do that in this order, as the value may just be exactly the same
    * as the previous one. In this context, think to reference counting,
    * you want to increment (set), and then decrement (free), and not the
    * reverse. */
    auxentry = *existing;
    dictSetVal(d, existing, val);
    dictFreeVal(d, &auxentry);
    return 0;
}

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    int index;
    dictEntry *entry;
    dictht *ht;

if (dictIsRehashing(d)) _dictRehashStep(d);

/* Get the index of the new element, or -1 if
    * the element already exists. */
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

/* Allocate the memory and store the new entry.
    * Insert the element in top, with the assumption that in a database
    * system it is more likely that recently added entries are accessed
    * more frequently. */
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

/* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;
}

主要逻辑在dictAddRaw中,也是先取得table中index,然后使用头插法插入到table的链表中。

如果dict处于rehash状态(即rehashidx !=  -1),则在插入的时候,先调用_dictRehashStep,对于rehash中的dict,使用的table是ht[1]。

static void _dictRehashStep(dict *d) {
    if (d->iterators == 0) dictRehash(d,1);
}

int dictRehash(dict *d, int n) {
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    if (!dictIsRehashing(d)) return 0;

while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/4dc303c4e08d7a6eb9e7693eede8056c.html