redis设计与实现——跳表
skiplist跳跃表是一种有序数据结构,通过在每个节点中维持多个节点中维持多个指向其他节点的指针。节点查找的时间复杂度,平均是O(logN),最坏是O(N),还可以批量操作处理节点。关于skiplist,可以参考论文。
在Redis中,跳跃表是作为有序集合键的底层实现基础。
跳跃表的实现
Redis的跳跃表是在server.h/zskiplistNode和server.h/zskiplist两个结构体中定义的,其中前者表示表节点,后者用来保存节点的数量,头部和尾部节点指针等相关信息。
跳跃表节点
跳跃表节点的结构体定义为:
1 | /* ZSETs use a specialized version of Skiplists */ |
- level:这是跳跃表节点的level数组,包含了多个元素,每个元素都不包含了一个指向前方其他节点的指针。span则是跨度,如上图所示,箭头上方的就是跨度数值,在查找某个节点的过程中,将沿途访过的所有层的跨度累积起来,就是当前节点所在的排位;
- backward:后退指针,只有一个后退指针意味着每次只能往前一个节点;
- score:跳跃表中所有节点按照分数值从小到大排序;
- ele:指向一个SDS字符对象,ele是唯一的,因此score相同时,需要按照sds在字典序中的大小排序;
跳跃表
跳跃表的结构体定义为:
1 | typedef struct zskiplist { |
redis使用zskiplist来管理所有的节点,比如表头节点和表尾节点,跳跃表长度以及level则表示在O(1)复杂度内获取跳跃表中层高最大的那个节点的层数量。
另外表头节点虽然与其他节点构造一样,但其后退指针、分值和ele等属性都不使用,并且不参与zskiplist中level的计算。
创建跳跃表
创建跳跃表的操作主要是完成一些初始化的操作,其时间复杂度为O(1)。创建操作主要依赖两个函数:zslCreate()和zslCreateNode()。其中redis默认跳跃表的最大层级为64。
1 | /* Should be enough for 2^64 elements */ |
插入新节点
新节点的插入是通过zslInsert实现的,给定分数值和ele元素则可返回新的跳跃表节点。
1 | zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) { |
其中随机层级的实现如下,redis的跳跃表最大层数为64,能够足够支撑优化2^64个元素的查找。其中获取随机层级时,越高的层级数出现的几率越小,而且每往上一个层级,其概率为1/4。
1 | /* Returns a random level for the new skiplist node we are going to create. |
总结一下
- 跳跃表时有序集合的底层实现之一;
- Redis跳跃表时由在server.h的zskiplistNode和zskiplist实现的;
- 每个跳跃表节点层高都是1-64的随机数;
- 多个节点可以包含相同的score,但ele时唯一的;
源码版本
截至2019/07/27,5.0之后的版本,commitId为:505a855000ef8f1fbea9cb41841fa8708175bba4