redis灵魂拷问:聊一聊redis底层数据结构

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: redis灵魂拷问:聊一聊redis底层数据结构

redis能具有很好的性能表现,一个重要的原因就是redis底层的数据结构的使用非常巧妙,今天,我们来聊一聊这些数据结构。


基本数据类型和数据结构对应


我们知道,redis有5种数据类型,包括字符串、列表、集合、有序集合和字典。同时redis底层的数据结构有6种,包括动态字符串、双向链表、压缩列表(ziplist)、hash表、跳表(skip list)和整数数组。


下面我们就看一下对应关系:



1.毋庸置疑,字符串类型使用的底层数据结构就是动态字符串。


2.关于列表,如果同时满足下面条件,就使用压缩列表,否则使用双向链表。

列表中单个元素小于64字节

列表中元素个数少于 512


3.关于集合,如果同时满足下面条件,就使用有序整数数组,否则使用hash表。

集合中元素都是整数类型

集合中元素个数不超过512


4.关于有序集合,如果同时满足下面2个条件,就使用压缩列表,否则使用跳表。

集合中元素都小于64字节

集合中元素个数小于128个


注意:有序集合还有一个hash表用于保存集合中元素的分数,做ZSCORE操作时,查询的就是这个hash表,所以效率很高。看下t_zset.c文件中这段注释:

* The elements are added to a hash table mapping Redis objects to scores.
 * At the same time the elements are added to a skip list mapping scores
 * to Redis objects (so objects are sorted by scores in this "view").

5.关于字典类型,如果同时满足下面2个条件,就使用压缩列表,否则使用hash表。

字典中每个entry的key/value都小于64字节

字典中元素个数小于512个


关于压缩列表


压缩列表并不是基础的数据结构,而是redis自己实现的,压缩列表跟数组相似,在内存中也是一块儿连续的内存空间,结构如下:

zlbytes

zltail

zllen

entry

entry

entry

....

entry

zlend

可以看到,在这块连续内存空间中,表头有3个字段,zlbytes表示压缩列表长度,zltail表示表尾的偏移量,zllen则表示列表中元素的个数。所以,压缩列表时间复杂度跟数组一样,都是o(n)。它的优势是实现了压缩存储。


关于hash表


hash表这种数据结构我们并不陌生,尤其是我们java程序员,HashMap可以说是面试的高频考题。其实redis中的hash表思想跟java中HashMap非常相似,我们一起看一下。


1.全局hash表

大家知道hash是时间复杂度最低的数据结构,复杂度是o(1), 底层使用一个数据存放hash桶。redis为了提高查询效率,使用了一个全局hash表,支持快速查询,而hash的key和value保存了指向实际key和value的指针*key和*value。


2.hash冲突

redis处理hash冲突的方法使用的是链表法,即如果2个key的hashcode一样,则在同一个hash桶中用一个链表组织起来,链表查找的时间复杂度是o(n),所以当hash桶中的元素越来越多时,查找性能会下降。这时redis就会做rehash。


3.rehash操作

redis会申请一个新的hash表,大小一般是当前hash的2倍,然后把就hash表的元素拷贝到新的hash表中,之后释放就hash表的空间。跟java中HashMap不一样的是,redis是单线程的,所以不用考虑并发问题。

当然,如果redis一次性拷贝这么多的元素,肯定造成线程阻塞,访问效率急剧下滑。所以,redis使用了渐进式的拷贝,就是每当访问一个entry时,顺带把这个entry拷贝到新的hash表中。


关于跳表


上面我们讲了,redis把所有的元素都组织在一个hash表中,所以对于字符串类型,查找效率直接就是o(1),非常快。但是通过key查找到对应的value后,对于value是压缩列表、整形数组、双向链表的情况,时间复杂度都是o(n),性能还是比较差的。为了提高性能,redis引入了跳表这种数据结构。

跳表这种数据结构,结构看起来跟B+树非常像,见下图:


下面是一颗B+树:

微信图片_20221212153915.png

下面是一个跳表:

微信图片_20221212153934.png

上面的跳表第一层是一级索引,第二层是二级索引。从上图中看出,如果我们不加索引,查找10这个数字需要查询10次,使用了二级索引,查找10这个数字需要5次,而使用一级索引,需要查询3次。跳表插入、删除、查询的时间复杂度是o(logN),效率还是很高的。但是跳表需要存储额外的索引节点,会增加额外的空间开销,所以也是空间换时间的一种数据结构。


总结


了解了redis底层的数据结构,我们使用的时候也就心里有数了。


对于redis的list数据类型,它用到了压缩列表和双向链表,时间复杂度是o(n),我们做随机读写是不太合适的,但是pop和push效率高,作为队列是很好的选择。


数组和压缩列表虽然查询复杂度高,但是都是连续的内存空间,对内存存储和缓存还是非常友好的。


最后,个人对redis理解有限,欢迎大佬们批评指正。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
25天前
|
存储 消息中间件 缓存
Redis 5 种基础数据结构?
Redis的五种基础数据结构——字符串、哈希、列表、集合和有序集合——提供了丰富的功能来满足各种应用需求。理解并灵活运用这些数据结构,可以极大地提高应用程序的性能和可扩展性。
28 2
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
43 5
|
1月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
215 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
29天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
54 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
52 4