如果没有记错的话,应该是在两个月前把 《Redis设计与实践》 这本书啃完了,确实是一本讲Redis的不可多得的好书,但是一直迟迟没有写自己的一些总结。一来是因为没有时间,二来是没有找到一个合适的思考点。

Redis本身支持很多种不同的类型,能让我们在不同的复杂的业务逻辑中游刃有余。Redis也可以说是万物皆对象,他就是一个个的键值对所组成,但是我们都知道,作为一款NoSql,虽然通过O(1)的方式能很快获取到数据,但是这也就暴露了他的一个弊端,对于范围查找,或者排序之类的无法很好的处理。

然而我们都知道,Redis有一个Sorted Set的有序集合对象,这能帮助我们实现范围查找,所以,为什么他能实现MySql此类关系型数据库才有的特性?而这也是我想写这篇文章的原因,也是作为自己对于《Redis设计与实践》这本书的一个总结。


我们经常看到此类的文章:

Redis的五种数据结构

Redis的数据结构以及对应的使用场景

其实以数据结构这个词去说明RedisStringHashListSetSorted Set是不够严谨和准确的,准确的来讲,这是Redis的五个对象,所以这也是我在上文为什么把有序集合称之为对象的原因。

  • 为什么这么说?

    其实我们仔细想想就知道了,不管我们使用PHP也好,Python也罢,我们好像很少会去讲PHP有哪几种数据结构,其实我们都知道我们想要阐述的是什么,但是,我个人认为用类型或者基础类型会更为准确,因为数据类型往往是指链表字典之类的字眼。

  • Redis的数据结构究竟有哪些?

    当然,Redis本身也是有自己的数据结构的,比如我们上面提到的五种对象,其实他们也是基于某些数据结构来实现的,具体包括:

    简单动态字符串(SDS)、链表、字典、跳跃表、整数集合、压缩列表。而这些数据结构,也就是我们所使用的五种对象的构建基础。当然,每种对象所使用的数据结构可能不止一种。


数据结构

简单动态字符串(SDS)

首先要介绍的就是SDS,简单动态字符串是Redis中大多数对象所需要使用的一个结构,比如我们设置一个简单的String:

set hello nine

此时其实String会创建两个SDS,分别用于报告键和值。

再比如:

rpush nine 18 male

此时会创建一个列表对象,而列表对象中的两个值又会使用两个SDS

SDS中有两个特点需要阐述一下:

  1. SDS的结构包括三个值:len用于记录长度,free用于记录所剩空间,buf用于记录内容。所以,这也能帮助我们很快的去获取到一个String的长度;
  2. 空间分配:我们每次给SDS添加值或者拦截值的时候,他会重新判断是否长度,如果是concat值,分配的空间是:
min(2 * len , 1M)

如果是截取字段,那么空间还保留,以便下次使用。

链表

链表是RedisList的底层实现,其实和我们经常所看到的双向链表一致,不过Redis的链表中有封装一个headtail用于指向表头和表尾,能帮助我们很快的找到头和尾。

字典

字典相对而言结构会复杂一些,下图是该书的作者所画的字典的结构图:

dict中有两个较为重要的,一个是ht数组,包含了两个dictht结构,为什么有两个,这样岂不是很占内存吗?的确,不过第二个dictht其实是为了减少我们在rehash中所耗费的时间而采用的一种解决方式。

rehashidx代表此字典是否在rehash的过程中。

dictht中的table用来管理数据,其中的0-n代表的是指向键值对的索引,而对应的keyvalue结构中还有一个next,这个可以帮助我们找到这个索引下面的另外的键值对。

size代表这个table的长度,sizemask代表哈希表的大小掩码,用于计算索引值,总是等于size - 1

其实前面也提到了一个问题,就是哈希表是比较占用空间的,所以我们应该有一个工具来定期给哈希表瘦身,这也就是我们前面提到的rehash,而前面我们所提到的ht中的另外一个dictht,就是用来做这个事情的:

当我们对数据进行CRUD的时候,我们在操作之后,会把这个键值对记录到第二个dictht中,然后删掉ht[0]中所对应的键值对,一旦当我们的ht[0]的长度为空,那么也就意味着我们的rehash已经完成,此时交换二者即可(这里其实有点像node v8引擎中的新生代的过程)。

但是,这里就有一个问题了,我在删除的过程中,如果我删掉了,但是还没完成rehash,那么我怎么获取数据呢?其实Redis也已经帮我们做完了这份工作,当我们在ht[0]中获取不到我们的值时,我们会到ht[1]中去查找。此外,Redis的字典还有一个非常智能的方式,当我们开始rehash之后,那么我们就不会再往ht[0]中添加任何元素,都会直接添加到ht[1]中,而这也就加快了我们rehash的过程。

跳跃表

跳跃表作为有序集合的实现方式之一,在Redis中也同样扮演者重要的角色。在网上没有找到很好的图示,所以自己根据书中的图花了一个对应的结构图:

其中的o1,o2...代表所绑定的成员对象。而上面的1.0之类的就代表对应的分值了,我们可以看到,跳跃表引入了层级的概念,这会帮助我们系统在查找范围时更加快捷和方便(为什么会更快可以参考这篇文章)。

上面这张图大概就是描述redis里面的一个跳跃表结构,score就是分数也就是节点的value值,查找的时候就是利用每一层的跨度从高到低去比较value的大小最终确定结果,也可以理解成从高到低不断的缩小查找范围,跟二分查找有点类似,这也就是我们上面说的跳跃表就是一个可以二分的链表,但是可以看到除了score还有一个span字段,正常的跳跃表是没有这个span字段的,而作者为了可以实现范围查找(分页)扩展了一个这个字段用来记录从高到低每个节点距离下一个节点之间的跨度。

同时,我们可以看到,在zskiplist中还有一个BW元素,代表后退指针,这个可以帮助我们倒序,也就是ZSET中用到的rev

整数集合

整数集合是集合的底层实现之一,当集合只包含整数并且个数较少时,会使用到整数集合,是一个无重复且按照大小排列的数组。

redis提供了一个方法帮助我们去识别所使用的数据结构:

sadd test 1 2 3
object encoding test
//intset

整数集合非常简单,不过有一个升级和降级的过程有点意思,可以稍微留意一下。在整数集合的结构中,有一个encoding用于记录集合的类型,取决于最大的数所占的空间,每次添加值时,会对值进行判断,如果超过了目前的encoding,那么就需要对整个数组进行升级,增加了灵活度,同时也节约了空间。降级同理。

压缩列表

压缩列表也是ListHash的实现方式之一,他的主要特点是数据紧凑在一起,放在一块连续的空间中,当我们使用时,如果字段本身没有超过一定的范围,同时整体的长度没有超过限制,那么就会使用压缩列表。比如:

rpush nine 18 male
object encoding nine
// ziplist

压缩列表有以下几个较为重要的属性:

zlbytes:记录整个压缩列表所占的内存字节数

zltail:尾部距离首部的距离

zllen:字节数量

zlend:标记末端

entryX:列表中所对应的节点

entryX结构中有一个重要属性:previous_entry_length代表前面一个节点的长度,所以当我们知道了某一个节点所对应的位置时,那么用这个位置减去previous_entry_length即可得到他的前一个节点的位置,这样就完成了遍历。


对象

前面已经讲完了我们所需要用到的Redis的几种数据结构,我们所使用的五种类型,正是基于这几种数据结构来实现的,但是前面我们也说过了,一种类型可以由不同的数据结构来实现,或者由几个数据结构来共同实现。下面我整理了一个表格,关于这五种类型的实现方式以及细节:

这是我的个人博客地址。

参考

  1. Redis设计与实现 online

聊一聊Redis的数据结构的更多相关文章

  1. Redis基本数据结构总结之STRING和LIST

    Redis基本数据结构总结前言 Redis的特点在于其读写速度特别快,因为是存储在内存中的,其非常适合于处理大数据量的情况:还有一个是其不同于其他的关系型数据库,Redis是非关系型数据库,也就是我们 ...

  2. Redis基本数据结构总结之SET、ZSET和HASH

    Redis基本数据结构总结 前言 Redis的特点在于其读写速度特别快,因为是存储在内存中的,其非常适合于处理大数据量的情况:还有一个是其不同于其他的关系型数据库,Redis是非关系型数据库,也就是我 ...

  3. Redis各种数据结构性能数据对比和性能优化实践

    很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. ...

  4. Redis学习——数据结构介绍(四)

    一.简介 作为一款key-value 的NoSQL数据库,Redis支持的数据结构比较丰富,有:String(字符串) .List(列表) .Set(集合) .Hash(哈希) .Zset(有序集合) ...

  5. Redis常用数据结构

    Redis常用数据结构包括字符串(strings),列表(lists),哈希(hashes),集合(sets),有序集合(sorted sets). redis的key最大不能超过512M,可通过re ...

  6. Redis的数据结构、通用操作及其特性

    Redis的数据结构 五种数据类型: 字符串(String).字符串列表(list).字符串集合(set).有序字符串集合(sorted set).哈希(hash) key定义的注意点: 不要过长,不 ...

  7. Redis学习笔记之Redis基本数据结构

    Redis基础数据结构 Redis有5种基本数据结构:String(字符串).list(列表).set(集合).hash(哈希).zset(有序集合) 字符串string 字符串类型是Redis的va ...

  8. 你真的懂redis的数据结构了吗?redis内部数据结构和外部数据结构揭秘

    Redis有哪些数据结构? 字符串String.字典Hash.列表List.集合Set.有序集合SortedSet. 很多人面试时都遇到过这种场景吧? 其实除了上面的几种常见数据结构,还需要加上数据结 ...

  9. Redis的数据结构

    Redis的数据结构 redis是一种高级的key-value的存储系统,其中value支持五种数据类型. 字符串(String) 哈希(hash) 字符串列表(list) 字符串集合(set) 有序 ...

随机推荐

  1. Python学习记录----类型匹配(转)

    import types aaa = 0 print type(aaa) if type(aaa) is types.IntType: print "the type of aaa is i ...

  2. 博弈论之Nim游戏

    Nim游戏是组合游戏(Combinatorial Games)的一种,属于“Impartial Combinatorial Games”(以下简称ICG). 通常的Nim游戏的定义是这样的:有若干堆石 ...

  3. 全面理解Javascript中Promise

    全面理解Javascript中Promise 最近在学习Promise的时候,在网上收集了一些资料,发现很多的知识点不够系统,所以小编特意为大家整理了一些自认为 比较好的文章,供大家更好地学习js中非 ...

  4. iOS设置圆角的常用方法

    //第一种方法:最常用的方法,但是性能最差 UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100 ...

  5. Python [目录]

    [第一篇] Python 发展历史 [第二篇] Python 优缺点 [第三篇] Python 应用场景 [第四篇] Python Hello World程序 [第五篇] Python 基础知识 [第 ...

  6. Typescript变量声明

    let 和 const 是 javascript 里面最新的变量声明方式,let 与 var 很相似,而 const 是 let 的增强,能阻止对一个变量的再次赋值. var 声明 弱类型:var 声 ...

  7. 在自学java路上遇上的南墙

    从2016年12月20号自学java,先是咨询了下培训中心,得小两万,四个月毕业,算了一笔账,一百二十天,合下来每天三百多块,再加上开销之类压力太大,于是开始入坑自学,随后血一般的教训直面而来: 1. ...

  8. JavaScript中的数组Array方法

    push(),pop()方法 push(),pop()方法也叫栈方法,push()可以理解成,向末尾推入,而pop()恰好相反,可以理解成从末尾移除(取得). var nums=[1,2,3,4]; ...

  9. 程序设计入门——C语言 习题汇总

    <img width="108" height="40" alt="浙江大学" src="http://imgsize.ph ...

  10. 关于dfs+剪枝第一篇:hdu1010

    最近进入了dfs关于剪枝方面的学习,遇到的第一道题就是hdu的1010.一道很基础的剪枝..可我不幸地wa了很多次(待会再解释wa的原因吧QAQ),首先我们来看一下题目. Problem Descri ...