首页 > 数据库 >Redis的跳跃表

Redis的跳跃表

时间:2024-04-07 23:44:44浏览次数:12  
标签:结点 zset Redis zskiplist zskiplistNode 链表 跳跃

在Redis中,有一种高效的数据结构叫做有序集合(zset)

它是一种集合,其中每个成员(member)都会关联一个分数(score)。
zset既可以快速地通过member找到该成员对应的分数,又可以按照分数的大小进行范围查询,这对于实现排行榜等功能非常有用。那么zset是如何实现这样的功能的呢?答案是跳跃表(skiplist)。

1. zset的底层实现

思考这么几个问题:

  • zset是如何快速的根据成员取得对应的分数?
  • zset是如何快速的取得指定排行区间的所有成员和分数?
  • zsetzrevrange(逆序)又是如何实现的?
  • zset如何计算一个成员的排位?
  • ...
    第一个问题我们很容易想到使用哈希表,实际上zset的底层实现,就是一个哈希表+跳跃表,看看结构:(对于元素数量少的情况,zset会使用另一种实现方式,我们这里不作讨论)
typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

哈希表(dict)大部分人都了解,那么对于这个跳跃表(zskiplist),又是怎样的结构呢?

2.跳跃表的结构和特性

跳跃表是一种有序链表+多层索引的结构,使得包含n个元素的有序序列的查找和插入操作的平均时间复杂度都是 \(O \left(\log{n}\right)\) 。
跳跃表是按层建造的,最底层是一个普通的有序链表。
众所周知,如果是一个有序的数组,我们可以通过二分查找,很快的找到其中的某个元素。那么对于一个有序的链表,我们有没有办法也使用二分查找呢?
跳跃表的想法就是用空间换时间:在最底层的原始链表上面,加多一层索引,元素数量为原始链表的一半,如图所示:

image

这样查找的时候,原本要比较n个元素,通过使用索引,减少到 \(\frac{n}{2}\) 。
但是这样还不够,所以我们在上面再多加几层索引,如图所示:
image
理论上,对于元素数量为n的链表,我们可以建立 \(\log_{2}{n}\) 层索引。
这样,我们就能通过从最高层开始,通过二分查找的方式,快速的查找某个数值在链表中的位置。

3.实现细节

Redis的跳跃表实现主要有两个结构:zskiplistNodezskiplist,其中zskiplistNode是跳跃表的结点,而zskiplist则记录整个表的一些信息,如结点的数量等。
skiplist

上图展示了一个跳跃表的实例。左边代表着跳跃表的整体结构:zskiplist

zskiplist的代码如下:

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

headertail:分别指向跳跃表头结点和尾结点,其中头结点在zskiplist创建时生成,并不实际存储数据。
length:跳跃表的长度,通过这个变量,可以在O(1)时间内获取长度。
level:当前跳跃表中最高的层,不包括头结点。

每次创建新结点的时候,redis根据幂次定律(越大的数出现概率越小)随机生成一个高度(介于1和32),作为当前结点的level数组的长度,也就是这个结点的高度。

结点zskiplistNode的代码如下:

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

ele:每个结点的成员,sdsredis内置的简单动态字符串结构(Simple Dynamic String)
score:每个结点的分数,是double类型
backward:回退指针,用于从表尾向表头遍历。用来实现zrevrange
level:每个层级上的索引。

注意level中的每一层,除了指向下一个结点的指针以外,还有一个span表示跨度,即下一个结点和当前结点的距离。(示例图中,箭头上面的数字)
这个跨度用来计算排行,由于redis的跳跃表并不是严格的按照每层数量减半的实现,所以在高层,每一次跨越几个原始结点,只能存储在这个span中,而在遍历的时候,通过计算跨度的和,就可以知道当前排在第几位了。

上图中红色和绿色都是zskiplistNode结构,其中headerbackward/ele/score都是没用的,所以省略了没画出来

4 与其他结构的对比

和红黑树这类平衡树结构相比,跳跃表的算法复杂度一样,而主要优势在于实现简单,且在并发环境下更加高效;
与哈希表相比,跳跃表支持有序数据的快速检索和范围查询,但牺牲了一些插入和删除操作的性能。
总的来说,跳跃表是一种空间换时间,以及用随机化处理平衡的高效数据结构。

参考资料:

标签:结点,zset,Redis,zskiplist,zskiplistNode,链表,跳跃
From: https://www.cnblogs.com/lcc9527/p/18120178

相关文章

  • 【知识点】Redis-缓存-缓存击穿
    缓存击穿:缓存中一个热点数据过期或失效时,由于该数据非常受欢迎,会有大量请求直接打到数据库上,导致数据库负载增大、相应变慢甚至瘫痪。解决方式:互斥锁在查询数据库之前首先获取分布式锁,更新redis之后再释放锁,可以保证数据的强一致性。优缺点:优点:强一致性缺点:性能差逻辑......
  • Redis持久化机制
    1.持久化机制Redis官方提供了两种不同的持久化方法来将内存的数据存储到硬盘里面分别是:-快照(Snapshot)-AOF(AppendOnlyFile)只追加日志文件1.1快照(Snapshot)1.特点这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis的默认开启持久化方式,保存的文......
  • Redis的前世今生(内存管理、持久化、高可用、集群 详解)一看就懂
    Redis的诞生:    redis的诞生和mysql脱不了关系,在redis还未出现时,用户的每次请求都是直接访问mysql,渐渐的人们发现,请求大部分都是读操作,而且很多都是重复的数据,磁盘的i/o是很慢的,所以人们就想,能不能学学cpu建立的缓存机制,mysql也搞一个缓存,就这样一个基于内存的数据库......
  • 为什么Redis 是单线程的以及为什么这么快?
    redis完全基于内存,绝大部分请求是纯粹的内存操作,非常快速.数据结构简单,对数据操作也简单,redis中的数据结构是专门进行设计的采用单线程模型,避免了不必要的上下文切换和竞争条件,也不存在多线程或者多线程切换而消耗CPU,不用考虑各种锁的问题,不存在加锁,释放锁的操作......
  • redis开发规约
    key设计1、可读性和可管理性#业务名:表名:idgetpersoninfo:person:12、简洁性减少key长度,建议不超过39字节例子humanresource:employee:88301->hr:emp_883013、特色字符key不要包含特殊字符(空格,换行,引号),建议使用英文与数字不同类型的应用场景 序列化JdkS......
  • Redis 一般有哪些使用场景?
    热点数据的缓存缓存是Redis最常见的应用场景,之所有这么使用,主要是因为Redis读写性能优异。而且逐渐有取代memcached,成为首选服务端缓存的组件。而且,Redis内部是支持事务的,在使用时候能有效保证数据的一致性。限时业务的运用redis中可以使用expire命令设置一个键的生存时间,......
  • redis基本操作
    基本类型string字符串#get/set-获取设置值setkey"value"#设置key的值为valuegetkey#获取key的值#getset-获取设置值getsetdbmongodb#没有旧值,返回nilgetsetdbredis#返回mongodb#setnx-nil时设置(分布式锁......
  • 使用redis-server &启动redis,没有读取到最新配置
    今天搭redis主从架构的时候,使用 redis-server& 启动redis时,一直没有读取到修改后的配置文件,得使用 redis-server/home/redis-7.2.4/redis.conf& 才可以后面查了下,redis.conf配置中开头有一段注释,说明为了读取配置文件,必须在启动Redis时将配置文件路径作为第一个参数传递......
  • java 企业工程管理系统软件源码+Spring Cloud + Spring Boot +二次开发+ MybatisPlus
    鸿鹄工程项目管理系统SpringCloud+SpringBoot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统项目背景一、随着公司的快速发展,企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,公司对内部工程管理的提升提出了更高的要......
  • java 企业工程管理系统软件源码+Spring Cloud + Spring Boot +二次开发+ MybatisPlus
     鸿鹄工程项目管理系统SpringCloud+SpringBoot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统项目背景一、随着公司的快速发展,企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,公司对内部工程管理的提升提出了更高的......