万字长文,38 图爆肝 Redis 基础!(三)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 万字长文,38 图爆肝 Redis 基础

02 Redis 的数据结构


2.6 压缩列表


压缩列表是 list 和 hash 的底层实现之一,当 list 只包含少量元素,并且每个元素都是小整数值,或者是比较短的字符串,压缩列表会作为 list 的底层实现。


压缩列表(ziplist)是 Redis 为节约内存而开发,它的理念是多大元素用多大内存


如下图,根据每个节点的实际存储的内容决定内存的大小,第一个节点占用 5 字节,第二个节点占用 5 字节,第三个节点占用 1 字节,第四个节点占用 4 字节,第五个节点占用 3 字节。


640.png


图示为 ziplist 的结构:它类似于一个数组,不同的是它在表头有三个字段 zlbytes、zltail 和 zllen;分别表示列表长度、列表尾的偏移量和元素的个数;表尾有 zlend,列表结束的标识。


640.png


2.6.0 节点构成


图示一个压缩列表中一个节点的构成:


640.png


  • previous_entry_length:记录前一个节点的长度
  • encoding:编码,控制 content 的类型和长度;分为字节数组编码和整数编码
  • content:保存节点值,可以是一个字节数组或整数


2.6.1 压缩列表的查找


640.png


如果查找的是第一个元素或最后一个元素,可通过表头三个字段的长度直接定位,复杂度是 O (1)。而查找其他元素时,只能逐个查找,复杂度是 O (N) 。


倒序遍历:首先指针通过 zltail 偏移量指向表尾节点,然后通过指向节点记录的前一个节点的长度依次向前遍历访问整个压缩列表


03 数据类型与数据结构


还记得文章开头那张数据类型与底层数据结构的对应关系图吗?长这样:


640.png


Redis 这种对应关系实际上是由 redisObject 的 type(类型)和 encoding (编码)共同决定的,详细对应关系如下:


640.png


下面来具体介绍下,什么条件下使用那种类型实现对应的对象。比如:String 什么情况下用 int 编码实现?什么情况下用 embstr 编码实现?什么情况下用 raw 编码实现呢?


3.0 字符串(String)对象


从上图得知,String 有 int、raw、embst 三种编码格式:


  • int:整数值,可以用 long 类型表示,使用整数值保存对象
  • raw:字符串值且长度 > 32 字节,使用 SDS 保存对象
  • embstr:字符串值且长度 < 32 字节,使用 embstr 编码的 SDS 保存对象


PS:对于浮点数(long double 类型表示的),Redis 会将浮点数转换成字符串值;最终视长度决定用那种编码(embstr 或 raw)保存。取出时,再将其转成浮点值。


640.png


3.0.0 embstr 和 raw 有啥区别?


  • raw 分配内存和释放内存的次数是两次,embstr 是一次
  • embstr 编码的数据保存在一块连续的内存里面


3.0.1 编码的转换


  • int 类型的字符串,当保存的不再是整数值,将转换成 raw 类型
  • embstr 类型的字符串是只读的,修改时会转换成 raw 类型。原因:Redis 没有为 embstr 提供修改程序,所以它是只读的;要修改只能先转成 raw。


3.1 列表(list)对象


还是从上图得知,列表的编码可以是 ziplist 或 linkedlist:


  • ziplist:所有元素长度都小于 64 字节且元素数量少于 512 个
  • 以上两个条件的上限值可以通过配置文件的 list-max-ziplist-valuelist-max-ziplist-entries 修改
  • linkedlist:不满足上述条件时,将从 ziplist 转换成 linkedlist


3.1.0 区别


执行 RPUSH 命令将创建一个列表对象,比如:


redis> RPUSH numbers 1 "three" 5
(integer) 3


如果 numbers 使用 ziplist 编码,对象结构如下:


640.png


否则使用 linkedlist,就是双端链表作为底层实现。结构如下:


640.png


3.2 哈希(hash)对象


又是从上图得知,哈希的编码可以是 ziplist 或 hashtable:


  • ziplist:哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节且键值对数量小于 512


  • 以上两个条件的上限值可以通过配置文件的 hash-max-ziplist-valuehash-max-ziplist-entries 修改


  • hashtable:不能满足上述条件,将从 ziplist 转成 hashtable


3.2.0 区别


执行 HSET 命令,可以创建一个 hash 对象并保存数据:


redis> HSET profile name "Tom"
(integer) 1
redis> HSET profile age 25
(integer) 1
redis> HSET profile career "Programmer"
(integer) 1


ziplist 保存的 hash 对象:


640.png


640.png


hashtable 保存的 hash 对象:


  • 字典中每个键都是一个字符串对像,对象中保存键值对的键
  • 字典中每个值都是一个字符串对像,对象中保存键值对的值


架构如下:


640.png


3.3 集合(set)对象


又又是从上图得知,哈希的编码可以是 intset 或 hashtable:


  • intset:集合对象保存的所有元素都是整数值且元素数量小于 512 个


  • 以上两个条件的上限值可以通过配置文件的 set-max-intset-entries 修改


  • hashtable:不能满足上述条件,将从 intset 转成 hashtable


3.3.0 区别


使用 SADD 命令可构建一个 intset 编码的 set 对象并保存数据:


redis> SADD numbers 1 3 5
(integer) 3


intset 编码的集合对象结构如下:


640.png


使用 SADD 命令可构建一个 hashtable 编码的 set 对象并保存数据:


redis> SADD fruits "apple" "banana" "cherry"
(integer) 3


hashtable 编码的 set 使用字典作为底层实现,每个键都是字符串对象,每个对象包含一个集合元素,字典值全部置为 null


hashtable 编码的集合对象结构如下:


640.png


3.4 有序集合(Sorted Set)对象


又又又是从上图得知,有序集合的编码可以是 ziplist 或 skiplist:


  • ziplist:保存的元素数量小于 128 个且所有元素长度都小于 64 字节


  • 以上两个条件的上限值可以通过配置文件的 zset-max-ziplist-entrieszset-max-ziplist-value 修改


  • skiplist:不能同时满足上述条件,将从 ziplist 转成 skiplist


3.4.0 区别


使用 ZADD 命令可以构建一个 Sorted Set 对象并保存数据:


redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3


640.png


ziplist 编码实现的 Sorted Set 对象,每个集合元素使用两个相邻的节点保存,第一个节点是元素成员,第二个节点是元素分值。按分值从小到大进行排序,结构如下:


640.png


skiplist 编码实现的 Sorted Set 使用 zset 作为底层实现,它包含 跳跃表字典,源码如下:


typedef struct zset {
    zskpilist *zsl;
    dict *dict;
}zset;


大体结构如下:


640.png


  • 跳跃表 zsl 按分值从小到大保存所有集合元素;每个节点保存一个集合元素;object 属性保存元素成员、score 属性保存元素分值。目的:实现快速的范围查询操作


  • 字典 dict 创建一个从成员到分值的 key-value;字典中每个键值对都保存一个集合元素;键保存元素成员、值保存元素分值。目的:用 O (1) 复杂度 get 元素分值


最后,详细的结构如下所示:


640.png


听到这里有人可能有疑问:zset 结构同时使用跳跃表和字典来保存有序集合元素,不会重复吗?


不会,因为二者会通过指针来共享同一个元素,并不会产生重复。


为什么 skiplist 编码实现的有序集合要同时用跳跃表和字典实现?随便用一个行吗


答案是:不好。我们来看看两种情况:


  • 只用 dict ,可以保留以 O (1) 复杂度 get 成员分值;但字典是无序的,所以每次进行范围操作都要对所有元素排序;显然这是性能更低的。


  • 只用跳跃表,快速范围操作得以保留;但是没了字典,get 成员分值的复杂度将提高至 O (logN),这也影响性能。


所以,Redis 为了把两者有点结合起来,采用了通过指针共享的方式,使用两种数据结构实现。


04 一些注意的点


4.0 Redis 如何执行命令


Redis 执行命令前,会先检查值对象类型,判断键是否能执行该命令;再检查值对象的编码方式选择合适的命令执行。


举个例子:列表对象有 ziplist 和 linkedlist 两种编码格式可用;前者通过 ziplist 的 API 执行命令、后者通过 linkedlist 的 API 执行命令。


如果我们执行 LLEN 命令,Redis 第一步判断执行的命令是不是针对列表的?是的话,第二步判断值的编码格式,如果是 ziplist,使用 ziplistLen 函数操作;如果是 linkedlist 则使用 listLength 函数操作。


4.1 Redis 内存回收机制与共享对象


Redis 为每个对象构建一个引用计数属性,通过它可实现内存回收机制(当一个对象的引用计数为 0 时,将会释放所占用内存)。


Redis 会共享值为 0 到 9999 的字符串对象(这个值可能通过修改 redis.h 文件的 REDIS_SHARDED_INTEGER 常量修改)


Redis 只共享字符串对象本身,为什么不共享包含字符串的对象


能共享的前提是目标对象和共享对象完全相同。要共享就需要验证两者是否相同?因为包含字符串的对象复杂度更高,验证消耗的 CPU 时间也更多,而性能将会下降。


4.2 lru 属性的作用


redisObject 的 lru 属性记录对象最后一次被访问的时间,这个时间可以用于计算对象的空转时间(公式:当前时间 - lru 时间)。


05 巨人的肩膀


  • 《Redis 设计与实现》
  • redis 源码:github.com/antirez/redis
  • redis 源码中文注释版:github.com/huangz1990/redis-3.0-annotated
  • cnblogs.com/Java3y/p/9870829.html
  • time.geekbang.org/column/article/268253
  • http://www.fidding.me/article/108
  • segmentfault.com/a/1190000019980165
  • cnblogs.com/chenchen0618/p/13260202.html


06 总结


本文从常用的缓存技术讲起,深入 Redis 的数据类型与底层数据结构。第一小节从 Redis 和缓存聊起;第二节站在源码角度跟你分析 Redis 的 6 种数据结构:SDS、链表、哈希表、跳跃表、整数集合以及压缩列表的特性;第三节着重和你分享 5 种数据类型和 6 中底层结构的对应关系;第四节则是画龙点睛地和你分享了 Redis 是怎么执行命令的?怎么释放内存等问题。


全文将近,张图,希望能帮到你。好啦,以上就是狗哥关于 MySQL 锁的总结。感谢各技术社区大佬们的付出,尤其是极客时间,真的牛逼。如果说我看得更远,那是因为我站在你们的肩膀上。希望这篇文章对你有帮助,我们下篇文章见~

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
NoSQL 开发工具 Redis
Redis学习13:服务器的基础配置
这个类似继承的意思。加速配置的一个东西。 服务器的配置比较独立一些,但配置并不是这么少,还有一些其他的。
Redis学习13:服务器的基础配置
|
NoSQL 安全 网络协议
Redis 使用基础及配置文件详解(三)|学习笔记
快速学习Redis 使用基础及配置文件详解(三)
293 0
|
NoSQL Java Linux
Linux java基础环境搭建 ->redis
Linux java基础环境搭建 ->redis
116 0
|
NoSQL Redis 数据库
【Docker 基础教程】容器数据持久化(三)------ Redis的基础配置
【Docker 基础教程】容器数据持久化(三)------ Redis的基础配置
【Docker 基础教程】容器数据持久化(三)------ Redis的基础配置
|
NoSQL 数据库 Redis
Redis基础的一些知识和命令
Redis基础的一些知识和命令
|
NoSQL Redis 数据库
Redis基础【完整版】:简介和常用命令、全面key操作、五种数据类型的增删改查、Redis与Python交互(附源代码)2
Redis基础【完整版】:简介和常用命令、全面key操作、五种数据类型的增删改查、Redis与Python交互(附源代码)
262 0
Redis基础【完整版】:简介和常用命令、全面key操作、五种数据类型的增删改查、Redis与Python交互(附源代码)2
|
存储 NoSQL 关系型数据库
Redis基础【完整版】:简介和常用命令、全面key操作、五种数据类型的增删改查、Redis与Python交互(附源代码)
Redis基础【完整版】:简介和常用命令、全面key操作、五种数据类型的增删改查、Redis与Python交互(附源代码)
319 0
Redis基础【完整版】:简介和常用命令、全面key操作、五种数据类型的增删改查、Redis与Python交互(附源代码)
|
监控 NoSQL Redis
Redis 6 新手入门基础篇
今天来讲讲redis以下知识点,如有不当请多指教!
200 0
Redis 6 新手入门基础篇
|
存储 缓存 NoSQL
Redis基础「5种基本数据结构」源码案例式深层讲解 建议观看收藏
**"Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker."** —— Redis是一个开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。(摘自官网)
127 0
Redis基础「5种基本数据结构」源码案例式深层讲解 建议观看收藏
|
存储 缓存 NoSQL
Java基础到就业!项目加面试!之Redis面试大全!
简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以存写速度非常快,因此 redis 被广泛应用于缓存方向。
157 0
Java基础到就业!项目加面试!之Redis面试大全!
下一篇
DataWorks