带你彻底击溃跳表原理及其Golang实现!(内含图解)

Posted 高可用架构

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你彻底击溃跳表原理及其Golang实现!(内含图解)相关的知识,希望对你有一定的参考价值。

* score; robj *obj; * span; level[]; zskiplistNode;


* length; level; zskiplist;


MaxLevel = p = Node value levels []*Level Level next *NodeSkipList header *Node length height * &SkipList header: NewNode(MaxLevel, length: height: * node := node.value = value node.levels = i := node.levels[i] = node(* r := rand. for r. level++ value <= update := tmp := sl.header i := tmp.levels[i].next != && tmp.levels[i].next.value < value tmp = tmp.levels[i].next tmp.levels[i].next != && tmp.levels[i].next.value == value update[i] = tmp level := sl.randomLevel() node := NewNode( sl.height = i := update[i] == sl.header.levels[i].next = node node.levels[i].next = update[i].levels[i].next update[i].levels[i].next = node sl.length++ node *Node last := tmp := sl.header i := tmp.levels[i].next != && tmp.levels[i].next.value < value tmp = tmp.levels[i].next last[i] = tmp tmp.levels[i].next!= node = tmp.levels[i].next node == i := last[i].levels[i].next = node.levels[i].next node.levels[i].next = i := sl.header.levels[i].next == sl.height = sl.length-- * node *Node tmp := sl.header i := tmp.levels[i].next != && tmp.levels[i].next.value <= value tmp = tmp.levels[i].next tmp.value == value node = tmp node (zobj->encoding == OBJ_ENCODING_ZIPLIST) (zobj->encoding == OBJ_ENCODING_SKIPLIST) zset *zs = zobj->ptr; zskiplistNode *znode; dictEntry *de; de = dictFind(zs->dict,ele); (de != (!xx) ele = sdsdup(ele); znode = zslInsert(zs->zsl,score,ele); serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK); *out_flags |= ZADD_OUT_ADDED; (newscore) *newscore = score; *out_flags |= ZADD_OUT_NOP;



四、回顾问题


前面提到,在看《Redis设计与实现》这本书时我有几点疑问,在详细了解跳表之后现在就完全理解了。


(一)为什么表头节点是不被计算在length属性里?


因为表头节点是初始化跳表时提供的空节点,不保存任何节点,只用于提供各级索引的入口。



(二)新增节点时是如何决定level的指针指向哪个后继节点?


通过分值和成员对象共同决定,判断新节点的插入位置和顺序。分值相同时,按成员对象首字母在字典的顺序确定先后。


经典跳表也同样需要一个维度来确定插入的顺序,我的跳表实现中直接使用了新节点的值作为排序的维度。



(三)为什么zset分值可以相同而成员对象不能相同?


根据第二个问题的答案,如果都相同,就无法确定插入的位置和顺序。



 作者简介


冯启源

腾讯后台开发工程师

腾讯后台开发工程师,毕业于中南大学,目前负责腾讯教育业务的后端开发工作,希望能用技术改善人们的生活。


参考阅读:


  • 从0到1:美团端侧CDN容灾解决方案

  • 百度搜索中台新一代内容架构:FaaS化和智能化实战

  • 五人基础架构组如何掌控千万DAU云原生架构

  • 代码质量第4层——健壮的代码!

  • 架构设计之道


  • 技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。


    高可用架构
    改变互联网的构建方式

    这次让你彻底学会redis中跳表原理,不懂你打我

    一、前言

    redis是一款优秀的内存高速缓存数据库,它支持较高的并发量。其中redis中是用跳表来索引数据的,本章就详细讲解一下跳表的原理。

    讲之前,我们现在身临其境的了解一下redis当时在选择跳表作为检索工具的初衷。

    现在有这样一个场景:内存中有几十万的数据,如何进行快速的检索,并且能快速的增、删、改、查呢。

    作为redis的作者,他可能有下面几种方案:

    方法1:用数据库来存储。

    这种方法弊端就在于速度太慢了。这要是放在高并发的情况下(比如:秒杀),还不得各种慢查询啊。

    方法2:有序数组来存储。

    数组来存储对于检索来说用折半查找速度非常快(时间复杂度:O(log2n) ),但是弊端就在于插入、删除太慢了。

    有序数组插入元素如下图:

    图中我们可以看出数组的插入需要将大量数据后移一位,有序数组插入一个数据的平均时间复杂度是O(n),这对于高并发,高吞吐量的场景来说还是太慢了。

    同样道理,有序数组删除一个元素,为了保持数组的有序需要将删除位置以后的数据都往前移动一个位置,平均时间复杂度也是(O(n))。

    关于如何计算

    以上是关于带你彻底击溃跳表原理及其Golang实现!(内含图解)的主要内容,如果未能解决你的问题,请参考以下文章

    带你彻底击溃跳表原理及其Golang实现!(内含图解)

    这次让你彻底学会redis中跳表原理,不懂你打我

    golang 实现跳表skiplist

    终于!12年后Golang支持泛型了!(内含10个实例)

    学习笔记Redis中有序集合zset的实现原理——跳表

    OAuth2.0 原理流程及其单点登录和权限控制