当前位置: 首页 > news >正文

【基础篇】数据结构

Redis 的底层数据结构

我们都知道 Redis 中的数据结构有:String,List,Hash,Set,Sort Set等,那么他们对应的底层的数据结构有哪些呢?
在这里插入图片描述
可以看到,String 类型的底层实现只有一种数据结构,即简单动态字符串。但是,其他四种数据结构,都有两种底层实现结构,它们被称为“集合类型”。

上面的数据结构都是值的底层实现,键和值之间用什么结构组织呢?

键与值之间的关联

为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。哈希表中每个元素称为一个哈希桶,每个哈希桶中保存了键值对数据。哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。
在这里插入图片描述
哈希表的最大好处很明显,就是让我们可以用 O(1) 的时间复杂度来快速查找键值对。

但是,我们往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。这其实是因为忽略了一个潜在的风险点,那就是哈希表的冲突问题和 rehash 可能带来的操作阻塞。

哈希表的哈希冲突

这里的哈希冲突,就是值两个 key 的哈希值和哈希桶计算对应关系的时候,正好落在同一个哈希桶里面。这一点很好理解,因为哈希桶的个数通常要少于 key 的数量。或者,你可以想象下:你有10 个篮子,12 个苹果,很明显,要全部放到篮子里的话,其中有两个篮子会有两个苹果。

Redis 中解决哈希冲突的方法,就是链式哈希。也即:同一个哈希桶中的多个元素,使用一个链表来保存:
在这里插入图片描述
这里回到我们之前说的点:往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。很明显,当数据过多的时候,一个哈希桶上的哈希链就会越长,那么查询耗时就会增大,效率降低。这对与我们来说是不能接受的,所以我们需要对哈希表做 rehash 操作。

rehash 就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中元素数量。具体操作如下:

  1. 为了 rehash 更有效率,Redis 默认使用两个全局哈希表:哈希表1 和哈希表2,一开始都使用 哈希表1;
  2. 当数据增多,要开始执行 rehash 时:
    • 给哈希表 2 分配更大的空间,比如说哈希表1 的两倍;
    • 把哈希表 1 中的数据重新映射到哈希表 2;
    • 释放哈希表 1 的空间;
  3. 哈希表 1就留着用于下一次 rehash 使用;
渐进式 rehash

上面的过程看似简单,但是数据的重新映射设计大量的数据拷贝,如果一次性将其迁移完成,会造成 Redis 线程阻塞。为了避免这个问题,Redis 采用了渐进式 rehash。

简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。
在这里插入图片描述
这样就巧妙地将一次性大量拷贝的开销,分摊到多次处理请求的过程中,避免了耗时操作。

集合类型的查找

对于 Sting 类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的 O(1) 操作复杂度也就是它的复杂度了。

但是,对于集合类型来说,即使找到哈希桶了,还要在集合中再进一步操作。接下来,我们来看集合类型的操作效率又是怎样的。也即:第一步是通过全局哈希表找到对应的哈希桶位置,第二步是在集合中再增删改查。

集合类型的底层包括 5 种:整数数组、双向链表、哈希表、压缩列表和跳表。
在这里插入图片描述

一些使用上的点

  1. 范围操作,是指集合类型中的遍历操作,可以返回集合中的所有数据,比如 Hash 类型的 HGETALL 和 Set 类型的 SMEMBERS,或者返回一个范围内的部分数据。这类操作的复杂度一般是 O(N),比较耗时,我们应该尽量避免。
  2. 整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么 Redis 还会把它们作为底层数据结构呢?
  • 内存利用率,数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。Redis是内存数据库,大量数据存到内存中,此时需要做尽可能的优化,提高内存的利用率。
  • 数组对CPU高速缓存支持更友好,所以Redis在设计时,集合数据元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。(这点和Hash 类似: Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构由压缩列表转为哈希表。 还有 String 的 SDS 底层也是类型)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 无缝融入,即刻智能[一]:Dify-LLM大模型平台,零编码集成嵌入第三方系统,42K+星标见证专属智能方案
  • 【Hot100】LeetCode—283. 移动零
  • [Spring] Spring AOP
  • 修复本地终端(windows)连接服务器使用zsh出现乱跳的问题
  • 有道云docx转换markdown,导入hugo发布到github page,多平台发布适配
  • 【Unity】案例 —— 胡闹厨房联机案例(持续更新)
  • css写一个按钮流光动画效果
  • Kubernetes-ingress
  • linux主机间免密登录
  • HBuidlerX 运行到Android App基座时提示没有检测到设备,该如何处理。
  • 002 | 常见的金融量化指标计算
  • Python电商网络数据采集实践||批量数据采集的API接口
  • 使用本地大模型从论文PDF中提取结构化信息
  • 深度学习--数据处理dataloader介绍及代码分析
  • 【C#】一个项目移动了位置,或者换到其他电脑上,编译报错 Files 的值“IGEF,解决方法
  • Babel配置的不完全指南
  • iOS 颜色设置看我就够了
  • js继承的实现方法
  • React Transition Group -- Transition 组件
  • Spring Cloud中负载均衡器概览
  • spring security oauth2 password授权模式
  • spring学习第二天
  • SQLServer之创建显式事务
  • 前端知识点整理(待续)
  • 深入浅出Node.js
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 详解移动APP与web APP的区别
  • 怎样选择前端框架
  • ​学习一下,什么是预包装食品?​
  • # centos7下FFmpeg环境部署记录
  • # Java NIO(一)FileChannel
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • #前后端分离# 头条发布系统
  • $().each和$.each的区别
  • (+4)2.2UML建模图
  • (rabbitmq的高级特性)消息可靠性
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (二)Eureka服务搭建,服务注册,服务发现
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (七)Appdesigner-初步入门及常用组件的使用方法说明
  • (转)linux下的时间函数使用
  • (转)大型网站的系统架构
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • .NET多线程执行函数
  • .NET框架
  • .NET中使用Protobuffer 实现序列化和反序列化
  • .pop ----remove 删除
  • @Validated和@Valid校验参数区别
  • [000-01-018].第3节:Linux环境下ElasticSearch环境搭建
  • [2023年]-hadoop面试真题(一)
  • [30期] 我的学习方法
  • [BZOJ1060][ZJOI2007]时态同步 树形dp