• Redis 数据结构之dict


    上篇文章《Redis数据结构概述》中,了解了常用数据结构。我们知道Redis以高效的方式实现了多种数据结构,因此把Redis看做为数据结构服务器也未尝不可。研究Redis的数据结构和正确、高效使用,对我们的应用程序会大有裨益。接下来的文章中我们逐个深入理解Redis的数据结构和使用场景。

    Redis数据结构,我们从Redis暴露给外部使用的数据结构和内部实现的两个角度来学习。

    1、暴露给外部使用的数据结构

    • string

    • list

    • hash

    • set

    • sort set

    2、内部实现基础结构即底层数据结构

    • dict

    • sds

    • intset

    • ziplist

    • skiplist

    • quicklist

    ...

    先抛出两个问题:Redis是如何组合 "内部实现" 的各种基础数据结构来实现 "暴露给外部使用" 的数据结构? "暴露给外部使用" 的数据结构与"内部实现" 的基础数据结构之间是什么样的关系?

    一、dict概述

         dict是一个用于维护key和value映射关系的数据结构,与很多语言中的Map或dictionary类似。Redis的一个database中所有key到value的映射,就是使用一个dict来维护的,即保存数据库的键值对。

           不过,dict在Redis中被使用的地方还有很多:

          1、一个Redis hash结构,当它的field较多时,便会采用dict来存储即Hash底层实现之一(另一种是通过压缩列表实现)。

           2、Redis配合使用dict和skiplist来共同维护一个sorted set。

    dict本质上是为了解决算法中的查找问题(Searching),一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表。我们平常使用的各种Map或dictionary,大都是基于哈希表实现的。在不要求数据有序存储,且能保持较低的哈希值冲突概率的前提下,基于哈希表的查找性能能做到非常高效,接近O(1),而且实现简单。

           在Redis中,dict也是一个基于哈希表的算法。和传统的哈希算法类似,它采用某个哈希函数从key计算得到在哈希表中的位置,采用拉链法解决冲突,并在装载因子(load factor)超过预定值时自动扩展内存,引发重哈希(rehashing)。

            Redis的dict实现最显著的一个特点,就在于它的重哈希。它采用了一种称为增量式重哈希(incremental rehashing)的方法,在需要扩展内存时避免一次性对所有key进行重哈希,而是将重哈希操作分散到对于dict的各个增删改查的操作中去。这种方法能做到每次只对一小部分key进行重哈希,而每次重哈希之间不影响dict的操作。dict之所以这样设计,是为了避免重哈希期间单个请求的响应时间剧烈增加,这与“快速响应时间”的设计原则是相符的。

    二、dict数据结构定义

        为了实现增量式重哈希(incremental rehashing),dict的数据结构里包含两个哈希表。在重哈希期间,数据从第一个哈希表向第二个哈希表迁移。

    我们看下Redis源码中dict.h

    /* Hash Tables Implementation.

     *

     * This file implements in-memory hash tables with insert/del/replace/find/

     * get-random-element operations. Hash tables will auto-resize if needed

     * tables of power of two in size are used, collisions are handled by

     * chaining. See the source code for more information... :)

     *

     * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>

     * All rights reserved.

     *

     * Redistribution and use in source and binary forms, with or without

     * modification, are permitted provided that the following conditions are met:

     *

     *   * Redistributions of source code must retain the above copyright notice,

     *     this list of conditions and the following disclaimer.

     *   * Redistributions in binary form must reproduce the above copyright

     *     notice, this list of conditions and the following disclaimer in the

     *     documentation and/or other materials provided with the distribution.

     *   * Neither the name of Redis nor the names of its contributors may be used

     *     to endorse or promote products derived from this software without

     *     specific prior written permission.

     *

     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE

     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR

     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF

     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS

     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN

     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)

     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE

     * POSSIBILITY OF SUCH DAMAGE.

     */

    dict定义dictEntry、dictType、dictht和dict四个结构体来实现散列表的功能。它们具体定义如下:

    1、dictEntry

    typedef struct dictEntry {

        void *key;

        union {

            void *val;

            uint64_t u64;

            int64_t s64;

            double d;

        } v;

        struct dictEntry *next;

    } dictEntry;

    哈希表节点,保存键值对的结构体。
    key:元素的key

    v:元素的值,只能存放一个被选中的成员

    next:后继节点即下一个键值对节点;

              处理哈希碰撞,所有分配到同一索引的元素通过next指针链接起来形成链表,key和v都可以保存多种类型的数据。

    从dictEntry的定义我们也可以看出dict通过“拉链法”来解决冲突问题。

    2、dictType

    typedef struct dictType {

        // hash方法,根据关键字计算哈希值

        unsigned int (*hashFunction)(const void *key);

        // 复制key

        void *(*keyDup)(void *privdata, const void *key);

        // 复制value

        void *(*valDup)(void *privdata, const void *obj);

        // 关键字比较方法

        int (*keyCompare)(void *privdata, const void *key1, const void *key2);

        //  销毁key

        void (*keyDestructor)(void *privdata, void *key);

       // 销毁value

        void (*valDestructor)(void *privdata, void *obj);

    } dictType;

    redis中通过dictType这样的一个结构用来存储针对不同类型的键值对的处理函数,即定义了字典操作的公共方法。这样对于不同类型的键值对,就可以有不同的处理了。即通过函数指针实现多态。

    3、dictht

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

    dictht哈希表结构,

    • table,散列数组(二级指针)真正存储数据的地方。可以将table看做一个指向数组的指针,而数组就是hash表最基本的结构。通过数组和hash节点中的next指针形成完整的hash表。

    • size,散列数组的长度,通常是2的整数次方。

    • sizemask,hash表大小掩码,用于计算索引,当size非0时为(size-1)

    • used,散列数组中已经被使用的节点数量

    4、dict

    typedef struct dict {

        dictType *type;

        void *privdata;

        dictht ht[2];

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

        int iterators; /* number of iterators currently running */

    } dict;

    dict,字典的主操作类,对dictht再次包装。

    • type,字典类型。处理函数表,通过其中保存的函数指针对不同类型的数据进行不同的处理,实现多态。

    • privdata,私有数据。

    • ht[2],哈希表,一个字典中有两个哈希表。

    • rehashidx,数据动态迁移时的下标位置。ht[0]中正在rehash的桶的索引,当rehash=-1时,表明此时没有在进行rehash操作。

    • iterators,当前正在使用的迭代器的数量。

    5、ictIterator

    /* If safe is set to 1 this is a safe iterator, that means, you can call

     * dictAdd, dictFind, and other functions against the dictionary even while

     * iterating. Otherwise it is a non safe iterator, and only dictNext()

     * should be called while iterating. */

    typedef struct dictIterator {

        dict *d;

        long index;

        int table, safe;

        dictEntry *entry, *nextEntry;

        /* unsafe iterator fingerprint for misuse detection. */

        long long fingerprint;

    } dictIterator;

    dictEntry、dictType、dictht和dict四个结构体之间的关系

    三、总结

    以上对dictEntry、dictType、dictht和dict四个结构体进行了说明,接下了对四个结构体的作用总结:

    一个dict由如下若干项组成:

    • 一个指向dictType结构的指针(type)。它通过自定义的方式使得dict的key和value能够存储任何类型的数据。

    • 一个私有数据指针(privdata)。由调用者在创建dict的时候传进来。

    • 两个哈希表(ht[2])。只有在重哈希的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据。

    • 当前重哈希索引(rehashidx)。如果rehashidx = -1,表示当前没有在重哈希过程中;否则,表示当前正在进行重哈希,且它的值记录了当前重哈希进行到哪一步了。

    • 当前正在进行遍历的iterator的个数。

    dictType结构包含若干函数指针,用于dict的调用者对涉及key和value的各种操作进行自定义。这些操作包含:

    • hashFunction,对key进行哈希值计算的哈希算法。

    • keyDup和valDup,分别定义key和value的拷贝函数,用于在需要的时候对key和value进行深拷贝,而不仅仅是传递对象指针。

    • keyCompare,定义两个key的比较操作,在根据key进行查找时会用到。

    • keyDestructor和valDestructor,分别定义对key和value的析构函数。

    私有数据指针(privdata)就是在dictType的某些操作被调用时会传回给调用者。

    dictht定义一个哈希表的结构,由如下若干项组成:

    • 一个dictEntry指针数组(table)。key的哈希值最终映射到这个数组的某个位置上(对应一个bucket)。如果多个key映射到同一个位置,就发生了冲突,那么就拉出一个dictEntry链表。

    • size:标识dictEntry指针数组的长度。它总是2的指数。

    • sizemask:用于将哈希值映射到table的位置索引。它的值等于(size-1),比如7, 15, 31, 63,等等,也就是用二进制表示的各个bit全1的数字。每个key先经过hashFunction计算得到一个哈希值,然后计算(哈希值 & sizemask)得到在table上的位置。相当于计算取余(哈希值 % size)。

    • used:记录dict中现有的数据个数。它与size的比值就是装载因子(load factor)。这个比值越大,哈希值冲突概率越高。

    dictEntry结构,包含k, v和指向链表下一项的next指针。

    k是void指针,这意味着它可以指向任何类型。

    v是个union,当它的值是uint64_t、int64_t或double类型时,就不再需要额外的存储,这有利于减少内存碎片。当然,v也可以是void指针,以便能存储任何类型的数据。

    -eof-

  • 相关阅读:
    进程与线程
    the art of seo(chapter seven)
    the art of seo(chapter six)
    the art of seo(chapter five)
    the art of seo(chapter four)
    the art of seo(chapter three)
    the art of seo(chapter two)
    the art of seo(chapter one)
    Sentinel Cluster流程分析
    Sentinel Core流程分析
  • 原文地址:https://www.cnblogs.com/exceptioneye/p/6855293.html
Copyright © 2020-2023  润新知