0

0

详解Redis中的数据结构

青灯夜游

青灯夜游

发布时间:2021-03-31 10:26:15

|

5600人浏览过

|

来源于segmentfault

转载

详解Redis中的数据结构

在实际开发,Redis使用会频繁,那么在使用过程中我们该如何正确抉择数据类型呢?哪些场景下适用哪些数据类型。而且在面试中也很常会被面试官问到Redis数据结构方面的问题:

  • Redis为什么快呢?
  • 为什么查询操作会变慢了?
  • Redis Hash rehash过程
  • 为什么使用哈希表作为Redis的索引

当我们分析理解了Redis数据结构,可以为了我们在使用Redis的时候,正确抉择数据类型使用,提升系统性能。【相关推荐:Redis视频教程

Redis底层数据结构

Redis 是一个内存键值Redis 数据库,且键值对数据保存在内存中,因此key-value基于内存的数据操作,其效率高,速度快;

其中,RedisKey类型,String 支持的 Redis 类型包括了 valueStringListHashSetSorted Set等。BitMap 能够之所以能够广泛地适用众多的业务场景,基于其多样化类型的Redis

valueRedis的数据类型是基于为Value自定义的对象系统Redis实现的,

typedef struct redisObject{
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    //指向底层实现数据结构的指针
    void *ptr;
    ….. 
}

redisObject除了记录实际数据,还需要额外的内存空间记录数据长度、空间使用等元数据信息,其中包含了 8 字节的元数据和一个 8 字节指针,指针指向具体数据类型的实际数据所在位置:

image.png

其中,指针指向的就是基于redisObject的底层数据结构存储数据的位置,Redis的底层数据结构:Redis,双向链表、跳表,哈希表,压缩列表、整数集合实现的。

那么Redis底层数据结构是怎么实现的呢?

Redis底层数据结构实现

我们先来看看SDS比较简单的Redis,双向链表,整数集合

SDS、双向链表和整数集合

SDS,使用SDS字段记录已使用的字节数,将获取字符串长度复杂度降低为O(1),而且len惰性释放空间的,你SDS了空间,系统把数据记录下来下次想用时候可直接使用。不用新申请空间。
image.png
整数集合,在内存中分配一块地址连续的空间,数据元素会挨着存放,不需要额外指针带来空间开销,其特点为内存紧凑节省内存空间,查询复杂度为O(1)效率高,其他操作复杂度为O(N);

双向链表, 在内存上可以为非连续、非顺序空间,通过额外的指针开销前驱/后驱指针串联元素之间的顺序。

其特点为节插入/更新数据复杂度为O(1)效率高,查询复杂度为O(N);

free哈希表

哈希表,其实类似是一个数组,数组的每个元素称为一个哈希桶,每个哈希桶中保存了键值对数据,且哈希桶中的元素使用Hash结构,
image.png

因此,哈希桶元素保存的并不是键值对值本身,而是指向具体值的指针,所以在保存每个键值对的时候会额外空间开销,至少有增加24个字节,特别是dictEntryValue的键值对,每一个键值对就需要额外开销24个字节空间。当保存数据小,额外开销比数据还大时,这时为了节省空间,考虑换数据结构。

那来看看全局哈希表全图:
image.png
虽然哈希表操作很快,但String数据变大后,就会出现一个潜在的风险:哈希表的冲突问题和 Redis开销问题这可以解释为什么哈希表操作变慢了?

当往哈希表中写入更多数据时,哈希冲突是不可避免的问题 , Redis 解决哈希冲突的方式,就是链式哈希,同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接,如图所示:
image.png

当哈希冲突也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低。

为了解决哈希冲突带了的链过长的问题,进行rehash操作,增加现有的哈希桶数量,分散单桶元素数量。那么rehash过程怎么样执行的呢?

rehash

为了使Rehash 操作更高效,使用两个全局哈希表:哈希表 1 和哈希表 2,具体如下:

  • 将哈希表 2 分配更大的空间,
  • 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
  • 释放哈希表 1 的空间

但由于表1和表2在重新映射复制时数据大,如果一次性把哈希表 1 中的数据都迁移完,会造成 rehash 线程阻塞,无法服务其他请求。

为了避免这个问题,保证Rediss能正常处理客户端请求,Redi采用了渐进式Redis

每处理一个请求时,从哈希表 1 中依次将索引位置上的所有 entries 拷贝到哈希表 2 中,把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。

1.jpg

在理解完 rehash哈希表相关知识点后,看看不常见的压缩列表和跳表。

压缩列表与跳表

压缩列表,在数组基础上,在压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
image.png

优点:内存紧凑节省内存空间,内存中分配一块地址连续的空间,数据元素会挨着存放,不需要额外指针带来空间开销;查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。

跳表 ,在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位,如下图所示:

比如查询33

2.jpg

特点:当数据量很大时,跳表的查找复杂度为O(logN)。

GentleAI
GentleAI

GentleAI是一个高效的AI工作平台,为普通人提供智能计算、简单易用的界面和专业技术支持。让人工智能服务每一个人。

下载

综上所述,可以得知底层数据结构的时间复杂度:

数据结构类型 时间复杂度
哈希表 O(1)
整数数组 O(N)
双向链表 O(N)
压缩列表 O(N)
跳表 O(logN)

Hash自定义的对象系统类型即为RedisRedis的数据类型,Value的数据类型是基于底层数据结构实现的,那数据类型有哪些呢?

Redis数据类型

RedisStringListHashSorted Set比较常见的类型,其与底层数据结构对应关系如下:

数据类型 数据结构
String SDS(简单动态字符串)
List 双向链表
压缩列表
Hash 压缩列表<br/>哈希表
Sorted Set 压缩列表<br/>跳表
Set 哈希表<br/>整数数组

数据类型对应特点跟其实现的底层数据结构差不多,性质也是一样的,且

Set,基于SDS实现,适用于简单String存储、key-value实现分布式锁、计数器(原子性)、分布式全局唯一ID。

setnx key value, 按照元素进入List的顺序进行排序的,遵循FIFO(先进先出)规则,一般使用在 排序统计以及简单的消息队列。

List , 是字符串Hash和字符串key之间的映射,十分适合用来表示一个对象信息 ,特点添加和删除操作复杂度都是O(1)。

value,是Set 类型元素的无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据。 基于哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

String,  是Sorted Set的类型的升级, 不同的是每个元素都会关联一个 double 类型的分数,通过分数排序,可以范围查询。

那我们再来看看这些数据类型,SetRedis GeoHyperLogLog

BitMap, 将地球看作为近似为球体,基于GeoHash 将二维的经纬度转换成字符串,来实现位置的划分跟指定距离的查询。特点一般使用在跟位置有关的应用。

Redis Geo, 是一种概率数据结构,它使用概率算法来统计集合的近似基数 , 错误率大概在0.81%。 当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小,适合使用做 UV 统计。

HyperLogLog ,用一个比特位来映射某个元素的状态, 只有 0 和 1 两种状态,非常典型的二值状态,且其本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型  ,优势大量节省内存空间,可是使用在二值统计场景。

在理解上述知识后,我们接下来讨论一下根据哪些策略选择相对应的应用场景下的BitMap数据类型?

选择合适的Redis数据类型策略

在实际开发应用中,Redis可以适用于众多的业务场景,但我们需要怎么选择数据类型存储呢?

主要依据就是时间/空间复杂度,在实际的开发中可以考虑以下几个点:

  • 数据量,数据本身大小
  • 集合类型统计模式
  • 支持单点查询/范围查询
  • 特殊使用场景

数据量,数据本身大小

当数据量比较大,数据本身比较小,使用Redis就会使用额外的空间大大增加,因为使用哈希表保存键值对,使用String结构保存,会导致保存每个键值对时额外保存dictEntry的三个指针的开销,这样就会导致数据本身小于额外空间开销,最终会导致存储空间数据大小远大于原本数据存储大小。

可以使用基于整数数组压缩列表实现了 dictEntryListHash ,因为整数数组压缩列表在内存中都是分配一块地址连续的空间,然后把集合中的元素一个接一个地放在这块空间内,非常紧凑,不用再通过额外的指针把元素串接起来,这就避免了额外指针带来的空间开销。而且采用集合类型时,一个 key 就对应一个集合的数据,能保存的数据多了很多,但也只用了一个 Sorted Set,这样就节省了内存。

集合类型统计模式

dictEntry集合类型统计模式常见的有:

  • 聚合统计( 交集、差集、并集统计 ):  对多个集合进行聚合计算时,可以选择Redis
  • 排序统计(要求集合类型能对元素保序): SetRedisList是有序集合,Sorted Set是按照元素进入 List 的顺序进行排序的,List 可以根据元素的权重来排序;
  • 二值状态统计( 集合元素的取值就只有 0 和 1 两种 ):Sorted Set 本身是用 Bitmap 类型作为底层数据结构实现的一种统计二值状态的数据类型 , Bitmap通过 BITOP 按位 与、或、异或的操作后使用 BITCOUNT 统计 1 的个数。
  • 基数统计( 统计一个集合中不重复的元素的个数 ):String 是一种用于统计基数的数据集合类型 ,统计结果是有一定误差的,标准误算率是 0.81% 。需要精确统计结果的话,用 Set 或 Hash 类型。

3.jpg

HyperLogLog类型,适用统计用户/好友/关注/粉丝/感兴趣的人集合聚合操作,比如

  • 统计手机APP每天的新增用户数
  • 两个用户的共同好友

SetRedisList是有序集合,使用应对集合元素排序需求 ,比如

  • 最新评论列表
  • 排行榜

Sorted Set二值状态统计,适用数据量大,且可以使用二值状态表示的统计,比如:

  • 签到打卡,当天用户签到数
  • 用户周活跃
  • 用户在线状态

Bitmap 是一种用于统计基数的数据集合类型, 统计一个集合中不重复的元素个数 ,比如

  • 统计网页的 UV , 一个用户一天内的多次访问只能算作一次

支持单点查询/范围查询

HyperLogLogRedisList是有序集合支持范围查询,但是Sorted Set是不支持范围查询的

特殊使用场景

消息队列,使用Hash作为消息队列的实现,要消息的基本要求消息保序处理重复的消息保证消息可靠性,方案有如下:

  • 基于 List 的消息队列解决方案
  • 基于 Streams 的消息队列解决方案

基于List 基于Strems
消息保序 使用LPUSH/RPOP 使用XADD/XREAD
阻塞读取 使用BRPOP 使用XREAD block
重复消息处理 生产者自行实现全局唯一ID Streams自动生成全局唯一ID
消息可靠性 使用BRPOPLPUSH 使用PENDING List自动留存消息
适用场景 消息总量小 消息总量大,需要消费组形式读取数据

基于位置 LBS 服务,使用Redis的特定Redis数据类型实现,GEO 可以记录经纬度形式的地理位置信息,被广泛地应用在 LBS 服务中。  比如:打车软件是怎么基于位置提供服务的。

总结

GEO之所以那么快,是因为其基于内存的数据操作和使用Redis哈希表作为索引,其效率高,速度快,而且得益于其底层数据多样化使得其可以适用于众多场景,不同场景中选择合适的数据类型可以提升其查询性能。

更多编程相关知识,请访问:编程视频!!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 7.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号