目录

Redis对象

对象编码

zset介绍

skiplist介绍

skiplist与平衡树、哈希表的比较

参考:


Redis对象


Redis对象由redisObject结构体表示。

typedef struct redisObject {unsigned type:4;            // 对象的类型,包括 /* Object types */unsigned encoding:4;        // 底部为了节省空间,一种type的数据,可以采用不同的存储方式unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */int refcount;         // 引用计数void *ptr;
} robj;

Redis中的每个键值对的键和值都是一个redisObject。

共有五种类型的对象:字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(SortedSet),源码server.h如下定义:

/* The actual Redis Object */
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

每种类型的对象至少都有两种或以上的编码方式;可以在不同的使用场景上优化对象的使用场景。用TYPE命令可查看某个键值对的类型

对象编码


Redis目前使用的编码方式:

/* Objects encoding. Some kind of objects like Strings and Hashes can be* internally represented in multiple ways. The 'encoding' field of the object* is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW     /* Raw representation */ 简单动态字符串
#define OBJ_ENCODING_INT      /* Encoded as integer */ 整数
#define OBJ_ENCODING_HT       /* Encoded as hash table */ 字典
#define OBJ_ENCODING_ZIPLIST  /* Encoded as ziplist */ 压缩列表
#define OBJ_ENCODING_INTSET   /* Encoded as intset */ 整数集合
#define OBJ_ENCODING_SKIPLIST   /* Encoded as skiplist */ 跳跃表
#define OBJ_ENCODING_EMBSTR  /* Embedded sds string encoding */ embstr编码的简单动态字符串
#define OBJ_ENCODING_QUICKLIST  /* Encoded as linked list of ziplists */

本质上,Redis就是基于这些数据结构而构造出一个对象存储系统。redisObject结构体有个ptr指针,指向对象的底层实现数据结构,encoding属性记录对象所使用的编码,即该对象使用什么数据结构作为底层实现。

zset介绍


有序集合对象的编码可以是ziplist或者skiplist。同时满足以下条件时使用ziplist编码:

  1. 元素数量小于128个
  2. 所有member的长度都小于64字节

以上两个条件的上限值可通过zset-max-ziplist-entries和zset-max-ziplist-value来修改。

ziplist编码的有序集合使用紧挨在一起的压缩列表节点来保存,第一个节点保存member,第二个保存score。ziplist内的集合元素按score从小到大排序,score较小的排在表头位置。

skiplist编码的有序集合底层是一个命名为zset的结构体,而一个zset结构同时包含一个字典和一个跳跃表。跳跃表按score从小到大保存所有集合元素。而字典则保存着从member到score的映射,这样就可以用O(1)的复杂度来查找member对应的score值。虽然同时使用两种结构,但它们会通过指针来共享相同元素的member和score,因此不会浪费额外的内存。

skiplist介绍


跳表(skip List)是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为O(logN)。简单说来跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能,正是这个跳跃的功能,使得在查找元素时,跳表能够提供O(logN)的时间复杂度。

先来看一个有序链表,如下图(最左侧的灰色节点表示一个空的头结点):

在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。

假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,如下图:

这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半(上图中是7, 19, 26)。现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中进行查找。比如,我们想查找23,查找的路径是沿着下图中标红的指针所指向的方向进行的:

  • 23首先和7比较,再和19比较,比它们都大,继续向后比较。
  • 但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与22比较。
  • 23比22要大,沿下面的指针继续向后和26比较。23比26小,说明待查数据23在原链表中不存在,而且它的插入位置应该在22和26之间。

在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。

利用同样的方式,我们可以在上层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第三层链表。如下图:

在这个新的三层链表结构上,如果我们还是查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。

skiplist正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)。但是,这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。删除数据也有同样的问题。

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

从上面skiplist的创建和插入过程可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。实际上,这是skiplist的一个很重要的特性,这让它在插入性能上明显优于平衡树的方案。这在后面我们还会提到。

skiplist,指的就是除了最下面第1层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针故意跳过了一些节点(而且越高层的链表跳过的节点越多)。这就使得我们在查找数据的时候能够先在高层的链表中进行查找,然后逐层降低,最终降到第1层链表来精确地确定数据位置。在这个过程中,我们跳过了一些节点,从而也就加快了查找速度。

刚刚创建的这个skiplist总共包含4层链表,现在假设我们在它里面依然查找23,下图给出了查找路径:

需要注意的是,前面演示的各个节点的插入过程,实际上在插入之前也要先经历一个类似的查找过程,在确定插入位置后,再完成插入操作。

实际应用中的skiplist每个节点应该包含key和value两部分。前面的描述中我们没有具体区分key和value,但实际上列表中是按照key(score)进行排序的,查找过程也是根据key在比较。

执行插入操作时计算随机数的过程,是一个很关键的过程,它对skiplist的统计特性有着很重要的影响。这并不是一个普通的服从均匀分布的随机数,它的计算过程如下:

首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。

如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那么它有第(i+1)层指针的概率为p。

节点最大的层数不允许超过一个最大值,记为MaxLevel。

skiplist与平衡树、哈希表的比较


skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。

平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。

从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。

查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
从算法实现难度上来比较,skiplist比平衡树要简单得多。

参考

  1. 《Redis内部数据结构详解(6)——skiplist》
  2. 《redis zset内部实现》

《Redis内部数据结构详解(6)——skiplist》

  • 2019-02-07» Redis源码从哪里读起?

  • 2017-02-24» 基于Redis的分布式锁到底安全吗(下)?

  • 2017-02-11» 基于Redis的分布式锁到底安全吗(上)?

  • 2016-11-22» Redis内部数据结构详解(7)——intset

  • 2016-10-30» 小白的数据进阶之路(上)——从Shell脚本到MapReduce

  • 2016-10-05» Redis内部数据结构详解(6)——skiplist

  • 2016-07-22» Redis内部数据结构详解(5)——quicklist

  • 2016-07-07» Redis内部数据结构详解(4)——ziplist

  • 2016-06-14» Redis内部数据结构详解(3)——robj

  • 2016-06-05» Redis内部数据结构详解(2)——sds

  • 2016-05-31» Redis内部数据结构详解(1)——dict

Redis zset(ziplist,skiplist)内部实现相关推荐

  1. Redis源码-ZSet:Redis ZSet存储原理、Redis ZSet命令、 Redis ZSet两种存储底层编码ziplist/dict+skiplist、Redis ZSet应用场景

    Redis源码-ZSet:Redis ZSet存储原理.Redis ZSet命令. Redis ZSet两种存储底层编码ziplist/dict+skiplist.Redis ZSet应用场景 Red ...

  2. redis zset怎么排序_redis(set、zset)类型使用和使用场景

    Redis的数据类型 Redis的数据类型共有五种:string,list,hash,set,zset: String 字符串相对来说做平常,key-value,类似是hashmap的用法: List ...

  3. Redis zset 底层数据结构之跳表

    参考: redis zset 内部的实现原理_行走在江湖的博客-CSDN博客_redis的zset实现原理 0.zset数据结构 [有序集合] [本质上是集合,所有元素不能重复] [分数可以重复(相同 ...

  4. redis的zset的底层实现_深入理解Redis Zset原理

    前言 最近把 AirNet 中的空气质量排行换成了用 Zset 实现,这篇笔记就来深入了解下 Zset 的底层实现原理. Zset 编码的选择 在通过 ZADD 命令添加第一个元素到空 key 时, ...

  5. redis zset转set 反序列化失败_7000字 Redis 超详细总结、笔记!建议收藏

    Redis 简介 Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key - value 数据库 Redis 与 其他 key - value 缓存产品有以下三个特点: Redis ...

  6. [redis]Redis ZSet排序问题(排名实现按时间顺序排布)

    背景: 需求: 承接之前mongo通过存储到mongo里面的战绩表计算出用户排名并将排名信息落库到mysql,但是由于用户排名需要频繁读取且其后续在计算排名后排名不会发生变动,所以将用户排名放到了re ...

  7. Redis入门(五):Redis set命令和 Redis zset命令

    Redis set 命令 Redis sorted set 命令 references https://github.com/Haiyoung/learning-and-preparing-for-i ...

  8. Redis ZSet数据结构实现排行榜功能

    Redis ZSet数据结构实现排行榜功能 一. 使用场景 公司新项目要求, 实现每日排行榜以及各省排行榜 二. 功能实现(Java) 1. 排行榜数据插入及更新 /*** @date: 2022/1 ...

  9. Redis Zset有序集合

    Redis Zset有序集合 1.zadd 添加一个或多个值 并且区分SCORE 2.zrange 返回有序集合区间 3.zrangebyscore 通过分数score排名,从低到高 4.Zrevra ...

最新文章

  1. CVPR | 让合成图像更真实,上交大提出基于域验证的图像和谐化
  2. 二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
  3. Dll注入经典方法完整版
  4. lru页面置换算法_C|内存管理|从LRU王国到NRU王国
  5. 如何让gridview中的checkbox根据数据库情况默认选中?
  6. C语言判断一个数是否是回文数Palindrome算法(附完整源码)
  7. python小数乘法_Polymorph:支持几乎所有现有协议的实时网络数据包操作框架
  8. python如何调用阿里云接口_python 调用阿里云云解析api添加记录
  9. 手机端仿ios的银行下拉脚本五
  10. sift算法的主要步骤
  11. 算法和数据结构~Sqlserver索引使用的B树
  12. webstorm主题
  13. 获取每周第一天和最后一天
  14. 沪深股市股票交易规则
  15. 二十一世纪大学英语读写教程学习笔记(原文)——10 - Cloning: good Science or Baaaad Idea(克隆技术是好科学还是馊主意)
  16. 43岁,转行当了大学老师
  17. 有没有能排列待办事项无广告的Windows版便签软件推荐
  18. html文本域 高度自适应,Javascript 文本域根据输入内容自适应高度
  19. Diskpart工具为硬盘进行GPT分区
  20. IBM沃森会成为第一个被抛弃的AI技术吗?

热门文章

  1. 它来了!IntelliJ IDEA 2020.1 稳定版发布
  2. leetcode题解172-阶乘后的零
  3. 方法 -------JavaScript
  4. UIScollView Touch事件
  5. 用eclipice抓取JS代码
  6. Oracle的dbms_jobs 自动计划
  7. DDL、DML、DQL、DCL 理解
  8. arcgis批量处理nc文件_ArcGIS处理NetCDF(.nc)的多维科学数据
  9. efcore 批量_【EF Core】Entity Framework Core 批处理语句
  10. python类的命名空间_Python之关于类变量的两种赋值区别详解