你确定不来了解下 Redis 跳跃表的原理吗

数据治
• 阅读 3672

前言

本章将介绍 Redis中 set 和 zset的基本使用和内部原理.因为这两种数据结构有很多相似的地方所以把他们放到一章中介绍.并且重点介绍zset 内部一个很重要的数据结构:跳跃表.

基本介绍

set

先来看看 set

Redis 中 set 集合很像Java 中 HashSet,键值对无序、唯一、不为空.

> sadd books Java
(integer 1)
> sadd books Java
(integer 0)            # value 值重复
> sadd books Go
(integer 1)
> smembers books        # 无序
1) "Go"
2) "Java"

zset

zset 是 Redis 中最特别的基础数据结构,其他几个都能和 Java 大致对应上.它基本上还是一个 set 但是添加了一个 score 属性去保证有序性.其内部实现为跳跃表稍后将会着重介绍.

> zadd books 1 Java
(integer) 1
> zadd books 2 Go
(integer) 1
> zadd books 3 Python
(integer) 1
> zrange books 0 -1     #按 score 有序取出
1) "Java"
2) "Go"
3) "Python"

在 zset 中 score 的类型为 double 所以有时会出现小数点精度问题.

当 zset 中最后一个 value 被删除后,这个和 zset 就会被自动删除,内存被回收.

内部原理

Redis 的 zset 是个复合结构,是由一个 hash 和 skiplist 组成的,其中 hash 用来保存 value 和 score 对应关系.skiplist 用来给 score 排序.关于hash 的内部实现请参阅之前的一篇文章:《你确定不来了解一下Redis中 Hash的原理吗》,在这里我们着重介绍 skiplist 的实现.

skiplist 跳跃表

因为zset需要高效的插入和删除,所以底层不适合使用数组实现,需要使用链表的结构.当插入新元素时需要根据 score插入到链表合适的位置,保证链表的有序性.高效的办法是通过二分查找去找到插入点.

那么问题就来了,二分查找的对象必须是有序数组,只有数组支持快速定位,链表做不到该怎么办呢?这时,就该跳跃表出场了.

你确定不来了解下 Redis 跳跃表的原理吗

如图所示,跳跃表在链表的基础上加入了层级L0~L3的概念,Redis 的跳跃表共有 64 层,可容纳 $2^{64}$ 个元素.每个元素的层级是随机分配的,分配 L0 的概率是 100%,就是说每个元素至少会有一层.分配L1 的概率是 50%,分配 L2 的概率是 25%,往上以此类推.

每个 kv 对应的结构为zslnode.kv 之间使用指针形成有序的双向链表.同一层的 kv 会使用指针串起来.每层元素的遍历都是从跳跃表的头指针 kv header 出发.

header 的结构也是 zslnode,当中 value 为 null,score 为 Double.MIN_VALUE排在最前面.

struct zslnode{
    string value;
    double score;
    zslnode*[] forwards;    //多层连接的指针
    zslnode* backward;        //回溯指针
}

struct zsl{
    zslnode* header;            //跳跃表头指针
    int maxLevel;                //当前节点的最高层
    map<String,zslnode*> ht;    //hash 中的键值对
}

查找

介绍完 skiplist的数据结构后,我们来具体看下skiplist 是怎样快速定位元素的.

在上图中,假设我们要查找 3 这个节点.skiplist 会从 header 的顶层出发遍历搜索找到第一个比目标元素小的开始降一层,直到降到最底层找到 3这个节点,搜索路径为:

  1. L3:header -> 4 -> header
  2. L2:header -> 2 -> 4 -> 2
  3. L1: 2 -> 4 ->2
  4. L0: 2 -> 3

说明:

  1. L3 层header找到 43大,回退到 header 同时降一层
  2. L2层header 找到 23 小,继续遍历找到 4,回退到 2 同时降一层
  3. L1 层 2 找到 43 大,回退到 2 降一层
  4. L0 层 2 找到 3 期望节点

整个查找过程算法的时间复杂度为$O(lg(n))$.

点赞
收藏
评论区
推荐文章
把帆帆喂饱 把帆帆喂饱
2年前
Redis入门
Redis1、Redis概述Redis介绍Redis是一个开源的keyvalue存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sortedset–有序集合)和
peter peter
4年前
Go-连接Redis-学习go-redis包
Redis介绍Redis是一个开源的内存数据结构存储,常用作数据库、缓存和消息代理。目前它支持的数据结构有诸如string、hash、list、set、zset、bitmap、hyperloglog、geospatialindex和stream。Redis内置了复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久性,并通过RedisSentinel
Stella981 Stella981
3年前
Django 之redis的应用
redis概述redis是一种nosql数据库,他的数据是保存在内存中,同时redis可以定时把内存数据同步到磁盘,即可以将数据持久化,并且他比memcached支持更多的数据结构(string,list列表队列和栈,set集合,sortedset有序集合,hash(hash表))
Stella981 Stella981
3年前
Redis 为什么这么快? Redis 的有序集合 zset 的底层实现原理是什么? —— 跳跃表 skiplist
Redis有序集合zset的底层实现——跳跃表skiplistRedis简介Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(Strings),散列(Hash),列表(List),集合(S
Stella981 Stella981
3年前
Redis——基础数据结构
摘抄自《redis深度历险》。  Redis提供了5种基础数据结构,分别是String,list,set,hash和zset。1、String  Redis所有的键都是String。Redis的String是动态字符串,内部结构类似Java的ArrayList和CSTL中的Vector。内部分配的容量capacity一般高
Stella981 Stella981
3年前
Redis为什么这么快
Redis简介Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件它支持多种类型的数据结构,如字符串(String),散列(Hash),列表(List),集合(Set),有序集合(SortedSet或者是ZSet)与范围查询,Bitmaps,Hyperloglogs和
Stella981 Stella981
3年前
Redis五种数据类型及应用场景
1、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。2、Redis支持数据的备份,即masterslave模式的数据备份。3、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。① string 是redis
Stella981 Stella981
3年前
Redis
Redis集合包括Set(无序集合)和ZSet(有序集合),这里的Set实现相当于Java中的HashSet,它内部实现了一个特殊的字典,字典中所有的value都是一个值NULL。下面我们来熟悉下set的常用的命令Setsadd namehelloZSetzset是一个有序集合,他有着java里的Sor
Stella981 Stella981
3年前
Redis基本数据结构总结之SET、ZSET和HASH
原文:Redis基本数据结构总结之SET、ZSET和HASH(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.cnblogs.com%2FGeorge1994%2Fp%2F7191011.html)Redis基本数据结构总结前言Re
Stella981 Stella981
3年前
Redis 避不开的五种数据结构
Redis中有5种数据结构,分别是字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(SortedSet),因为使用Redis场景的开发中肯定是无法避开这些基础结构的,所以熟练掌握它们也就成了一项必不可少的能力。本文章精要地介绍了Redis的这几种数据结构,主要覆盖了它们各自的定义、基本用法与相关要点。字
跳跃表数据结构与算法分析
目前市面上充斥着大量关于跳跃表结构与Redis的源码解析,但是经过长期观察后发现大都只是在停留在代码的表面,而没有系统性地介绍跳跃表的由来以及各种常量的由来。作为一种概率数据结构,理解各种常量的由来可以更好地进行变化并应用到高性能功能开发中。本文没有重复地以对现有优秀实现进行代码分析,而是通过对跳跃表进行了系统性地介绍与形式化分析,并给出了在特定场景下的跳跃表扩展方式,方便读者更好地理解跳跃表数据结构。