跳跃表

这是一个类似二分查找的算法,我们可以简单的理解为是链表+多级索引

跳跃表 skiplist 就是受到这种多层链表结构的启发而设计出来的。按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到 O(logn)

但是,这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的 2:1 的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点 (也包括新插入的节点) 重新进行调整,这会让时间复杂度重新蜕化成 O(n)。删除数据也有同样的问题。

skiplist 为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是 为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是 3,那么就把它链入到第 1 层到第 3 层这三层链表中。为了表达清楚,下图展示了如何通过一步步的插入操作从而形成一个 skiplist 的过程:

https://zhuanlan.zhihu.com/p/109946103

Redis 跳跃表默认允许最大的层数是 32,被源码中 ZSKIPLIST_MAXLEVEL 定义,当 Level[0] 有 264 个元素时,才能达到 32 层,所以定义 32 完全够用了。

 

为什么用跳表

红黑树真好,但是跳表真香。

为什么跳表香?

  • 好实现

  • 索引便利,可以快速找到区间中批量数据,更方便做批操作。 zrangebyscore

至于为啥说好实现、好做批量操作,自己手写个跳表直观感受一下就好了呀。

为什么不用跳表

  • 如果数量级少,索引级数小的情况下,随机构建插入节点存在偶发的低效。

  • 高度扩容后顺序大量插入数据会导致扩容前数据索引不会被高级索引cover

 

为啥 redis 使用跳表(skiplist)而不是使用 red-black? 跳表插入效率比红黑色高,而且要做线程安全处理也比红黑色简单

点击展开内容

There are a few reasons:

1) They are not very memory intensive. It's up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.

2) A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.

3) They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.

About the Append Only durability & speed, I don't think it is a good idea to optimize Redis at cost of more code and more complexity for a use case that IMHO should be rare for the Redis target (fsync() at every command). Almost no one is using this feature even with ACID SQL databases, as the performance hint is big anyway.

About threads: our experience shows that Redis is mostly I/O bound. I'm using threads to serve things from Virtual Memory. The long term solution to exploit all the cores, assuming your link is so fast that you can saturate a single core, is running multiple instances of Redis (no locks, almost fully scalable linearly with number of cores), and using the "Redis Cluster" solution that I plan to develop in the future.

 

有几个原因:

1) 它们不是很占用内存。基本上由你决定。更改关于节点具有给定级别数的概率的参数将使其比btree占用更少的内存。

2) 排序集通常是许多ZRANGE或ZREVRANGE操作的目标,即作为链表遍历跳过列表。通过此操作,跳过列表的缓存局部性至少与其他类型的平衡树一样好。

3) 它们更易于实现、调试等。例如,由于跳过列表的简单性,我收到了一个补丁(已经在Redis master中),其中包含在O(log(N))中实现ZRANK的扩展跳过列表。它只需要对代码进行少量更改。

关于仅附加的耐用性和速度,我不认为以更多代码和更复杂的用例为代价优化Redis是一个好主意,因为对于Redis目标来说IMHO应该是罕见的(每个命令都使用fsync()。几乎没有人使用这个特性,即使是在ACID-SQL数据库中,因为性能提示无论如何都很重要。

关于线程:我们的经验表明Redis主要是I/O绑定的。我正在使用线程从虚拟内存提供服务。利用所有核心的长期解决方案是,假设您的链接速度如此之快,您可以使单个核心饱和,则运行多个Redis实例(无锁,几乎完全可随核心数线性扩展),并使用我计划在未来开发的“Redis群集”解决方案。

 

posted @ 2022-04-08 16:20  敲键盘的猫  阅读(104)  评论(0编辑  收藏  举报