redis中的数据结构(2)|青训营

72 阅读5分钟

skiplist

跳跃表是一个有序链表,其中每个节点包含不定数量的链接,节点中的第i个链接构成的单向链表跳过含有少于i个链接的节点。 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,大部分情况下,跳跃表的效率可以和平衡树相媲美。

跳跃表的实现


typedef struct zskiplistNode {
    robj *obj;                          //保存成员对象的地址
    double score;                       //分值
    struct zskiplistNode *backward;     //后退指针
    struct zskiplistLevel {
        struct zskiplistNode *forward;  //前进指针
        unsigned int span;              //跨度
    } level[];                          //层级,柔型数组
} zskiplistNode;
//表头
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;//header指向跳跃表的表头节点,tail指向跳跃表的表尾节点
    unsigned long length;       //跳跃表的长度或跳跃表节点数量计数器,除去第一个节点
    int level;                  //跳跃表中节点的最大层数,除了第一个节点
} zskiplist;

层:跳跃表的level数组可以包含多个元素,每个元素都包含一个指向其它节点的指针,程序可以根据这些层来加快访问其它节点的速度,一般来说,层的数量越多,访问其它节点的速度就越快每次创建个一个跳跃表节点的时候,Redis都会根据幂次定律(越大的数出现的概率越小)随机生成一个介于1-32之间的值作为level数组的大小。也就是高度。

幂次定律

在redis中,返回一个随机层数值,随机算法所使用的幂次定律。其含义是:如果某件事的发生频率和它的某个属性成幂关系,那么这个频率就可以称之为符合幂次定律。其表现是:少数几个事件的发生频率占了整个发生频率的大部分, 而其余的大多数事件只占整个发生频率的一个小部分。在这里我们可以看看redis中幂次算法的源码


int zslRandomLevel(void) {          //返回一个随机层数值
    int level = 1;
    //(random()&0xFFFF)只保留低两个字节的位值,其他高位全部清零,所以该值范围为0到0xFFFF
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))  //ZSKIPLIST_P(0.25)所以level+1的概率为0.25
        level += 1;         //返回一个1到ZSKIPLIST_MAXLEVEL(32)之间的值
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */

ziplist

"ziplist"是Redis中一种用于存储有序集合(sorted set)和哈希表(hash)的内部数据结构。它是一种紧凑且高效的数据结构,用于存储小型的有序集合或哈希表。在Redis中,当一个有序集合或哈希表满足特定条件时,会选择使用ziplist作为其底层数据结构。ziplist通过将多个元素连续存储在一块内存中,并使用特定的编码方式来压缩数据,从而节省了内存空间。ziplist可以在特定场景下提供更好的性能,比如当有序集合或哈希表的元素数量较少或元素长度较短时。相比于其他数据结构,如跳跃表(skip list)和哈希表(hash table),ziplist具有更小的内存占用和更高的访问效率。ziplist的结构如图所示

IMG_2.png

zlbyte:整个压缩列表所占的字节数,4byte。

zltail_offset:记录压缩列表尾节点entryN距离压缩列表的起始地址的字节数

zllenth:压缩列表的结点数量。

entry:保存数据

zlend:结束标志位。

zlist的结点结构

zlist虽然定义了entry结构保存结点信息,但实际上是使用宏定义的结构来保存结点的内容,从而更加节省空间。其整体结构如图所示

IMG_3.png prev_entry_len:记录前驱节点的长度。 encoding:记录当前节点的value成员的数据类型以及长度。 value:根据encoding来保存字节数组或整数

quiklist

Redis的快速列表(Quicklist)是一种内部数据结构,用于存储和操作列表数据。它被设计为在存储大量元素的情况下提供高性能和低内存消耗。快速列表使用一种分层的方式来存储列表元素。它将列表划分为多个ziplist节点,每个节点可以容纳多个元素。这种分层结构允许快速列表在处理较短的列表时非常高效,在处理较长的列表时也能保持较低的内存占用。当列表中的元素数量较少时,快速列表可以直接使用一个ziplist节点来存储所有的元素。当元素数量越来越多时,快速列表会将元素拆分到多个ziplist节点中,并使用一个压缩列表(跳跃表)来存储这些ziplist节点的指针。通过这种方式,快速列表可以在保持较小的内存开销的同时,提供类似于普通列表的操作效率。例如,它可以在常数时间内执行头部和尾部元素的插入、删除和查找操作。 quicklist结构在quicklist.c中的解释为A doubly linked list of ziplists意思为一个由ziplist组成的双向链表。接下来介绍quicklist与ziplist的关系:quicklist是由ziplist组成的双向链表,链表中的每一个节点都以压缩列表ziplist的结构保存着数据,而ziplist有多个entry节点,保存着数据。相当与一个quicklist节点保存的是一片数据,而不再是一个数据。 quicklist宏观上是一个双向链表,因此,它具有一个双向链表的有点,进行插入或删除操作时非常方便,虽然复杂度为O(n),但是不需要内存的复制,提高了效率,而且访问两端元素复杂度为O(1)。 quicklist微观上是一片片entry节点,每一片entry节点内存连续且顺序存储,可以通过二分查找以 log2(n) 的复杂度进行定位。

IMG_4.png