一个基于运气的数据结构,你猜是啥? (上)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 一个基于运气的数据结构,你猜是啥? (上)

image.png


排行榜


懂行的老哥一看这个小标题,就知道我要以排行榜作为切入点,去讲 Redis 的 zset 了。

是的,经典面试题,请实现一个排行榜,大部分情况下就是在考验你知不知道 Redis 的 zset 结构,和其对应的操作。

当然了,排行榜我们也可以基于其他的解决方案。比如 mysql。

我曾经就基于 mysql 做过排行榜,一条 sql 就能搞定。但是仅限于数据量比较少,性能要求不高的场景(我当时只有 11 支队伍做排行榜,一分钟刷新一次排行榜)。

对于这种经典的面试八股文,网上一找一大把,所以本文就不去做相关解析了。

说好的只是一个切入点。

如果你不知道具体怎么实现,或者根本就不知道这题在问啥,那一定记得看完本文后要去看看相关的文章。最好自己实操一下。

相信我,八股文,得背,这题会考。


zset 的内部编码


众所周知,Redis 对外提供了五种基本数据类型。但是每一种基本类型的内部编码却是另外一番风景:

image.png


其中 list 数据结构,在 Redis 3.2 版本中还提供了 quicklist 的内部编码。不是本文重点,我提一嘴就行,有兴趣的朋友自己去了解一下。

本文主要探讨的是上图中的 zset 数据结构。

zset 的内部编码有两种:ziplist 和 skiplist。


image.png


实你也别觉得这个东西有多神奇。因为对于这种对外一套,对内又是一套的“双标党”其实你已经很熟悉了。

它就是 JDK 的一个集合类,来朋友们,大胆的喊出它的名字:HashMap。

HashMap 除了基础的数组结构之外,还有另外两个数据结构:一个链表,一个红黑树。

这样一联想是不是就觉得也不过如此,心里至少有个底了。

当链表长度大于 8 且数组长度大于 64 的时候, HashMap 中的链表会转红黑数。

对于 zset 也是一样的,一定会有条件触发其内部编码 ziplist 和 skiplist 之间的变化?

这个问题的答案就藏在 redis.conf 文件中,其中有两个配置:

image.png

上图中配置的含义是,当有序集合的元素个数小于 zset-max-ziplist-entries 配置的值,且每个元素的值的长度都小于 zset-max-ziplist-value 配置的值时,zset 的内部编码是 ziplist。

反之则用 skiplist。

理论铺垫上了,接下我给大家演示一波。

首先,我们给 memberscore 这个有序集合的 key 设置两个值,然后看看其内部编码:

image.png

此时有序集合的元素个数是 2,可以看到,内部编码采用的是 ziplist 的结构。

为了大家方便理解这个储存,我给大家画个图:

image.png

然后我们需要触发内部编码从 ziplist 到 skiplist 的变化。

先验证 zset-max-ziplist-value 配置,往 memberscore 元素中塞入一个长度大于 64字节(zset-max-ziplist-value默认配置)的值:

image.png

这个时候 key 为 memberscore 的有序集合中有 3 个元素了,其中有一个元素的值特别长,超过了 64 字节。

此时的内部编码采用的是 skiplist。

接下来,我们往 zset 中多塞点值,验证一下元素个数大于 zset-max-ziplist-entries 的情况。

我们搞个新的 key,取值为 whytestkey。

首先,往 whytestkey 中塞两个元素,这是其内部编码还是 ziplist:

image.png

那么问题来了,从配置来看 zset-max-ziplist-entries 128

这个 128 是等于呢还是大于呢?

没关系,我也不知道,试一下就行了。

现在已经有两个元素了,再追加 126 个元素,看看:

image.png

通过实验我们发现,当 whytestkey 中的元素个数是 128 的时候,其内部编码还是 ziplist。

那么触发其从 ziplist 转变为 skiplist 的条件是 元素个数大于 128,我们再加入一个试一试:

image.png

果然,内部编码从 ziplist 转变为了 skiplist。

理论验证完毕,zset 确实是有两幅面孔。

本文主要探讨 skiplist 这个内部编码。

它就是标题说的:基于运气的数据结构。


什么是 skiplist?


这个结构是一个叫做 William Pugh 的哥们在 1990 年发布的一篇叫做《Skip Lists: A Probabilistic Alternative to Balanced Trees》的论文中提出的。

论文地址:ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf

我呢,写文章一般遇到大佬的时候我都习惯性的去网上搜一下大佬长什么样子。也没别的意思。主要是关注一下他们的发量稀疏与否。

image.png


在找论文作者的照片之前,我叫他 William 先生,找到之后,我想给他起个“外号”,就叫火男:


image.png


他的主页就只放了这一张放荡不羁的照片。然后,我点进了他的 website:


image.png

里面提到了他的丰功伟绩。

我一眼瞟去,感兴趣的就是我圈起来的三个地方。

  • 第一个是发明跳表。
  • 第二个是参与了 JSR-133《Java内存模型和线程规范修订》的工作。
  • 第三个是这个哥们在谷歌的时候,学会了吞火。我寻思谷歌真是人才辈出啊,还教这玩意呢?

eat fire,大佬的爱好确实是不一样。

感觉他确实是喜欢玩火,那我就叫他火男吧:


image.png


火男的论文摘要里面,是这样的介绍跳表的:


image.png


摘要里面说:跳表是一种可以用来代替平衡树的数据结构,跳表使用概率平衡而不是严格的平衡,因此,与平衡树相比,跳表中插入和删除的算法要简单得多,并且速度要快得多。

论文里面,在对跳表算法进行详细描述的地方他是这样说的:


image.png


首先火男大佬说,对于一个有序的链表来说,如果我们需要查找某个元素,必须对链表进行遍历。比如他给的示意图的 a 部分。

我单独截取一下:


image.png


这个时候,大家还能跟上,对吧。链表查找,逐个遍历是基本操作。

那么,如果这个链表是有序的,我们可以搞一个指针,这个指针指向的是该节点的下下个节点。

意思就是往上抽离一部分节点。

怎么抽离呢,每隔一个节点,就抽一个出来,和上面的 a 示意图比起来,变化就是这样的:


image.png


抽离出来有什么好处呢?

假设我们要查询的节点是 25 。

当就是普通有序链表的时候,我们从头节点开始遍历,需要遍历的路径是:

head -> 3 -> 6 -> 7 -> 9 -> 12 -> 17 -> 19 -> 21 -> 25

需要 9 次查询才能找到 25 。

但是当结构稍微一变,变成了 b 示意图的样子之后,查询路径就是:

第二层的 head -> 6 -> 9 -> 17 -> 21 -> 25。

5 次查询就找到了 25 。

这个情况下我们找到指定的元素,不会超过 (n/2)+1 个节点:


image.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
算法 搜索推荐 C++
【数据结构】手撕排序(二)
【数据结构】手撕排序(二)
|
存储 算法
【手撕数据结构】(一)时间复杂度
【手撕数据结构】(一)时间复杂度
85 0
|
7月前
|
存储 算法 Serverless
数据结构期末复习(六)查找算法
数据结构期末复习(六)查找算法
48 0
|
7月前
【手撕数据结构】二分查找(好多细节)
【手撕数据结构】二分查找(好多细节)
|
7月前
|
存储 算法 NoSQL
刻意练习:数据结构复习思路
刻意练习:数据结构复习思路
|
7月前
|
搜索推荐 算法
【数据结构】手撕排序(一)
【数据结构】手撕排序
|
存储 算法
【手撕数据结构】(二)空间复杂度
【手撕数据结构】(二)空间复杂度
85 0
|
算法 搜索推荐 编译器
数据结构入门(C语言版)一篇文章教会你手撕八大排序(下)
这里采用的是C++的写法,方便调用队列,想用C语言写的小伙伴可以参考博主之前关于队列的博客,进行调用修改,步骤相差无几。
|
搜索推荐
数据结构入门(C语言版)一篇文章教会你手撕八大排序(上)
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
|
机器学习/深度学习 算法 C语言
追梦之旅【数据结构篇】——看看小白试如何利用C语言“痛”撕堆排序
建堆 升序:建大堆 降序:建小堆 利用堆删除思想来进行排序建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
57 0