Redis系列之——深入了解Redis的String,源码层级+极易理解图片解析!

数据库之Redis运维手册(私人整理) 1、redis配置文件 #cat 8000.conf daemonize no #[yes/no]默认值no,该参数用于定制redis服务是否以守护模式运行。 pidfile /var/run/redis.pid #默认值/var/run/redis.pid,指定redis服务的进程号文件路径,以守护模式运行时需要配置本参数; port 8000 #默认值6379,指定redis服务... 阅读详情

Redis的数据结构

Redis最常用的数据类型有五种

  • String: 字符串
  • Hash: 散列
  • List: 列表
  • Set: 集合
  • Sorted Set: 有序集合

五种其实是Redis键值对中值存储的数据类型,而他们的底层数据结构一共有6种:分别是

  • 简单动态字符串
  • 双向链表
  • 压缩列表
  • 哈希表
  • 跳表
  • 整数数组

数据类型和数据结构的对应关系如下图:

这张图会在未来几篇文章中反复出现,帮大家彻底了解Redis的基础类型。

今天我们就来聊一聊其中的string

Redis是用C写的,那为什么不用C语言的String?

众所周知,Redis是用C语言写的,那Redis为什么没有使用C原生的字符串,而是自己创建了一个简单动态字符串?(SDS simple dynamic string)

  • C语言的字符串底层是用字符数组来实现的,在一片连续的空间中依次存放字符,为了判断字符的结束,他会在最后以'\0'作为识别,这样就会带来以下问题

    1. 无法存放任意的字符,至少'\0'是不可以的,这就会导致一些如图片,音频等出现了'\0'就会出现问题。
    2. 对字符串进行追加等操作的时候,必须遍历到'\0'才可以操作,会导致效率比较低,复杂度为o(n)
  • C语言的字符串是不记录字符串长度的,一旦我们调用了拼接函数等,而没有提前计算好内存,就会产生缓冲区溢出的情况,所以为了不出问题,会进行内存重分配,而这又多出了内存重分配的性能损耗。

那么,Redis是怎么处理这些问题的呢?

简单动态字符串(Redis5.0版本)

Redis中的字符串数据是通过简单动态字符串(以下简称SDS)来存储数据的。

SDS到底是什么?

我们先来看看SDS的结构长什么样

  • len:表示buf的已用长度。(sdshdr8中占1个字节)
  • alloc:表示分配给buf的总长度,不包括结构体和'\0'结束字符。(sdshdr8中占1个字节)
  • flags: SDS类型。(sdshdr8中占一个字节)
  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。(nycdf--> 你也才掉发是我在这里写的示例,代表存储的某个数据)

你丫才掉发小课堂--详解SDS类型

SDS 结构中的字段 flags,表示的是SDS的类型。在Redis中SDS一共设计了5种类型,分别是

  • sdshdr5(未使用)
  • sdshdr8
  • sdshdr16
  • sdshdr32
  • sdshdr64

这5种类型的主要区别就在于,它们数据结构中的len和alloc,这两个字段占据的大小不同,也就是这个结构体能存储的长度不同。下面他们是结构体的源码:

从源码我们可以看出来,其实最大的不同就是len和alloc所代表的字节数不同,那么比如sdshdr8中这2个字段的类型uint8_t,也就是这个类型结构最大存储的字符长度为256(2的8次方),其他的以此类推。

这样的设计目的:Redis是基于内存的,而内存永远都是珍贵的资源,每一个字节都很重要,所以针对不同大小的字符串使用不同的结构,也是为了节省内存资源。

在SDS中,除了上面解释过的flags,对比于C传统的char[],SDS新增了2个参数,实际占用长度len 和总分配长度alloc。

SDS解决了什么问题呢?

1. 获取字符串长度的复杂度问题

上面我们说过,c语言遍历一下字符串的复杂度是o(n)。

对于SDS来说,我们有了长度的参数,那么我们的复杂度就是o(1),这样在进行拼接等操作的时候不需要再次遍历了。

2. 存储二进制数据

C字符串不能存储二进制数据的原因是只能根据'\0'来判断数据是否结束,不能保证其完整性,但因为SDS的len属性,无论你数据里有多少'\0'都没关系,我是根据 len 属性值来判断数据长度的,必定是完整的,所以SDS可以安全地存储二进制数据,这样图片音频等二进制数据也可以存储。

你丫才掉发小课堂

Q:SDS你都有len了,那为啥还要跟C字符串一样在字符串后面加'\0',是不是多此一举了?

A:SDS如果保持和C字符串一样的特性,就不用专门编写多余的函数了,在一定的情况下可以直接用C语言的函数,避免了不必要的重写。

3. 分配内存空间问题

上面还在存在一个问题是在使用拼接等操作的时候C语言是需要重分配空间的,而这个又是非常的消耗资源。SDS提供的2个特性就是空间预分配 和 惰性空间释放

空间预分配

空间预分配源码

在真正执行拼接等操作之前,会先计算下空间是否满足

  1. 如果计算后的SDS的长度(源码中的newLen)小于1MB,那么就会分配和len属性同样大的未使用空间,相当于将原数组长度乘以二。

  1. 如果计算后的SDS的长度(源码中的newLen)大于或等于1MB,那么就会分配固定的1MB未使用空间。

所以当有N次拼接操作时候,一定需要N次的内存重分配操作,现在最多只需要N次。

比如你第一次拼接后,剩余的长度(alloc - len)就大于后续要拼接的字符串长度之和了,那其实就只有1次内存重分配操作,就可以进行至少2次拼接操作,所以说最多N次,这也就可以省下很多分配空间的消耗。

惰性空间释放

当有缩短SDS字符串操作时,程序并不立即把空闲出来的字节释放掉,而是留给到惰性删除策略等机制释放。

具体的表现就是:

在做删除操作的时候,当删掉N个字符后,alloc-len(也就是剩余空间)就增加N,换个说法,就是你删除的字符并没有立即被系统回收,这样当你短时间内再次拼接的时候,就不会申请空间分配了。

只有到了对应策略或者手动去删除的时候才会执行以下的代码:

代码的逻辑就是删除多余的空间直到没有浪费为止。

有了SDS,就万事大吉了么?

前面我们聊完了SDS,已经大概的知道了SDS是什么样的,解决了什么问题,这里给大家提出一些更深层的问题:

  1. 如果我的string是数字,Redis也按照字符串存储么?
  2. string在Redis中就是只有SDS就可以落地了么,有没有别的结构支撑?
  3. SDS最大支持存储长度为2的64次方的数组,这些超长的字符串他们是怎么存储的?

初识RedisObject

在我们真实使用Redis中string的时候,除了SDS的存储空间,还存在一个Redis本身的结构体,也就是RedisObject。

RedisObject存储空间

以上是RedisObject源码,

  • type:redisObject的数据类型,4个bits
  • encoding:redisObject的编码类型,4个bits
  • lru:LRU_BITS:redisObject的LRU时间,LRU_BITS为24个bits
  • refcount:redisObject的引用计数,4个字节
  • *ptr:指向值的指针,8个字节

为了形象的展示占据的大小:

RedisObject和String

这里暂时不对RedisObject展开讲,我们来看看当RedisObject和SDS在一起的时候大概是个什么样子?

上图表示的就是普通情况下RedisObject和SDS的关系。

作为Redis最常用的结构之一,针对String也做了很多的优化

1. 数字类型的字符串

对于数字类型的字符串,Redis在RedisObjec中的指针就直接赋值为整数数据了,这么指针的开销就省去了。

2. embstr编码方式(embed:嵌入式)

在保存的是字符串数据,如果字符串小于等于44字节时(为什么是44呢?读到这里可以思考一下),Redis就会将RedisObjec和SDS构建成一块连续的内存区域,既可以提升查询的速度(少了一次指针的跳转),也可以防止内存碎片的产生。

我们先来看下这里判断的源码:

  • 当字节数小于44,调用createEmbeddedStringObject
  • 当字节数大于44,调用createRawStringObject

这里我们来看下createEmbeddedStringObject的部分源码

这里做的事情核心

  1. 先分配一段连续的内存空间(RedisObject+SDS+1的长度,这里的1是为了字符串最后的`'\0'),并创建RedisObject和SDS的机构。
  2. 将RedisObject的ptr指针指向字符数组的开始。(细节!!!!)
  3. 拷贝字符串到SDS的buf[]中。

这个时候内存块包含如下几部分:

  • 16个字节的robj结构。
  • 3个字节的sdshdr8头。
  • 最多44个字节的sds字符数组。
  • 1个'\0'结束符。

3. raw编码模式

在保存的是字符串数据,字符串大于44字节时,Redis就不像embstr编码方式一样把RedisObject和SDS存放在一起,这时RedisObject中的*ptr就会发挥真正的作用,Redis在内存中重新分配一块空间给SDS,并用*ptr指向这个SDS。

这里我们来看下createRawStringObject的部分源码

 

这里就比较简单了,一共做了这2件事:

  1. 先使用sdsnewlen返回SDS结构的指针
  2. 将SDS指针赋值给RedisObject中的指针

最后再给大家把所有的编码格式放一起汇总一下:👇

Redis字符串面试题深度解析:128M限制是最大误区! 场景推荐命令避坑要点分布式锁值需随机数,避免误删高并发计数器INCRINCRBY避免操作非整数key缓存大对象分片存储或外部存储单Key不超过10MB二进制数据存储直接SET无需Base64编码(SDS直接支持)频繁修改的字符串预分配大空间减少内存重分配次数终极建议定期扫描牢记512MB上限二进制数据无需编码:SDS直接存储原始字节理解Redis字符串的底层机制,不仅是为了应对面试,更是规避生产事故的关键。“我的数据真的符合SDS的设计预期吗?扩展阅读。 阅读详情

相关推荐

归因分析计算因子贡献度常见的方法

在归因分析中,我们一般都需要计算出每个因子的贡献度是多少,比如产品DAU上升,对年龄段维度进行拆解,看是不同年龄段的用户对DAU上升的贡献度是多少,一般根据指标的类型,计算贡献度的方法也不一样,下面就列出一些常见的归因分析贡献度的计算方法。

Hubans的博客 4万+

Redis 6.2 redis创建一个字符串对象是如何选择合适的编码方式(源码)

通过Redis 6.2 key的创建SDS(源码)知道String在redis中是有多种编码方式存储的,但是对一段字符串如何进行选择编码方式就不清楚了,下面就是redis在保存数据时,字符串编码方式如何选择的源码 文章目录当键值对的value是字符串时,入口方法选择String类型的编码方式createRawStringObject(OBJ_ENCODING_RAW 编码方式)createEmbeddedStringObject(OBJ_ENCODING_EMBSTR 编码方式) 当键值对的value是.

胖墩的IT博客 1196

3D识别建模 — 基于ReconstructMe和EAGLE EYES SCANNER的实现

3D实体识别建模,作为3D打印反向工程的一个重要环节,今天就让我们利用ReconstructMe和EAGLE EYES SCANNER来了解一下。 第一步、软件和硬件准备就绪 ReconstructMe下载并安装 http://reconstructme.net/?wpdmdl=160 EAGLE EYES SCANNER直接将相应USB插入电脑接口即OK,之后在    中点击搜索“设备管理...

qq_40013290的博客 882

Redis源码-String:Redis String命令、Redis String存储原理、Redis String三种编码类型、Redis字符串SDS源码解析、Redis String应用场景

编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换(不包括重新set),重新set虽然在值上是修改或插入,但在Redis看来是重新赋值,这里的编码转换指的修改比如说拼接字符串、自增这种,非set的改值操作。Redis的key是字符类型,即String,最大可以用得到是512M,那么它的value字符类型,也是String,最大也是512M。中文标点占三个字节,英文标点占一个字节。1,ASCII码:一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。.

郝开的博客 1820

Informer:比Transformer更有效的长时间序列预测

目录 AAAI 2021最佳论文:比Transformer更有效的长时间序列预测 Background Why attention Methods:the details of Informer Solve_Challenge_1:最基本的一个思路就是降低Attention的计算量,仅计算一些非常重要的或者说有代表性的Attention即可,一些相近的思路在近期不断的提出,比如Sparse-Attention,这个方法涉及了稀疏化Attention的操作,来减少Attention计算量,然后涉及的

To be a better man 1万+

Android逆向分析

Android 逆向开发是指对已发布的 Android 应用进行分析和破解,以了解应用程序的内部工作原理,获取应用程序的敏感信息,或者修改应用程序的行为。Android应用 Java编写的,利用Android SDK编译代码,并并且把所有的数据和资源文件打包成一个APK(Android Package)文件这是一个后缀名为.apk的压缩文件,apk文件中包含了一个Anddroid应用程序的所有内容,是Android平台用于安装应用程序的文件。

qq_51598534的博客 6294

Redis之object源码阅读

在这片博客中,我们将对object.c这个文件的其中一些方法进行阅读,大部分代码都是类似的: createObject:创建一个新对象 robj *createObject(int type, void *ptr) { robj *o = zmalloc(sizeof(*o)); o->type = type; o->encoding = REDIS_ENC...

m0_37343985的博客 228

YOLO26 改进 | (教师:YOLOv8L,学生:YOLO26) | 知识蒸馏 (Knowledge Distillation)

本文介绍了基于YOLO26框架的知识蒸馏实战方法,通过教师模型指导学生模型训练,在不增加推理成本的情况下提升模型精度。文章详细阐述了基于响应的知识蒸馏实现方案,通过最小化教师与学生模型预测Logits间的KL散度实现知识迁移。核心实现包括:1) 创建DistillationModel包装器类,同时运行师生模型;2) 自定义DistillationLoss,在原检测损失基础上增加KL散度损失。该方法适用于希望保持YOLO26轻量级特性同时获得接近大模型精度的场景,文中提供了完整的代码实现方案。

weixin_51988935的博客 510

redis五大基本数据类型之(源码分析)

在redis6当中这个数据类型的编码主要是而在redis7当中的物理编码对应是。

qq_56999918的博客 935

Google Earth Engine:计算逐年全球潜在蒸发量(PET)的 ERA 数据

通过获取温度、湿度和降水数据,并使用Penman-Monteith公式,我们可以得到每年的潜在蒸发量。在本文中,我们将使用Google Earth Engine平台和ERA(欧洲再分析气象数据集)数据来计算逐年全球的潜在蒸发量(PET)。接下来,我们需要对ERA数据进行预处理,以获取我们需要的潜在蒸发量数据。有了温度、湿度和降水数据,我们可以使用Penman-Monteith公式来计算每年的潜在蒸发量。最后,我们可以将计算得到的潜在蒸发量数据进行可视化,以便更好地理解全球的水汽蒸发情况。

VwajAlgorithm的博客 734

YOLO26涨点改进 | 独家主干改进篇 | CVPR 2024 | YOLO26利用InceptionNeXt主干,将大核深度卷积分解为四个并行分支,在提升性能的同时显著降低计算效率

本文聚焦YOLO26的核心涨点改进——基于CVPR 2024收录的InceptionNeXt主干网络,深度拆解了其“大核深度卷积四分支拆解”的核心逻辑,通过实验验证了改进的有效性,同时提供了可快速复现的工程代码与进一步涨点的思路。

qq_36130719的博客 575

Redis系列之——深入了解Redis的String,源码层级+极易理解图片解析

在上一篇文章中,我和大家介绍了Redis的前世今生,,这一篇就和大家一起揭秘Redis神秘的面纱,第一个我们就来聊一聊Redis数据类型中的。

java1527的博客 154

【YOLO系列】YOLOv8详解:模型结构、损失函数、训练方法及代码实现

YOLOv8是Ultralytics团队2023年推出的高效目标检测框架,在速度与精度平衡、多任务支持和易用性方面实现突破。模型采用改进的CSPDarknet主干网络、C2f模块和SPPF加速结构,结合PAN-FPN特征融合和解耦检测头设计,支持目标检测、实例分割和关键点检测任务。创新性地使用CIoU定位损失、Focal Loss置信度损失和动态样本分配策略,显著提升检测性能。实验表明,YOLOv8较前代模型mAP提升5-10%,在TensorRT加速下可达1000FPS,提供5种尺寸灵活选择,成为实时目标

一碗白开水一 2780

Redis String 类型深度解析与最佳实践

在当今的互联网时代,数据存储和快速访问是许多应用的核心需求。想象一个在线购物平台,用户在浏览商品时,系统需要实时展示商品信息,包括价格、库存等。这些信息需要频繁地被读取和更新,如果使用传统的数据库系统,可能会因为频繁的读写操作导致性能瓶颈。这时,Redis作为一种高性能的键值存储系统,就成为了解决这一问题的理想选择。Redis中的String类型数据结构,正是实现这类快速读写操作的关键。

我是Java程序员廖志伟,感谢朋友们的支持!不定期贡献一篇高品质、过万文字、图文并茂且附有视频解说、满载代码示例注释的良心之作,坚决杜绝粗制乱造。 690

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Redis中String数据类型原理实现

微信公众号:Java患者专注Java领域技术分享String 数据模型首先Redis是KV数据结构,跟JDK中的Map是一样的,Redis是通过hashtable实现的,我们把这个叫做外...

ZeJava的博客 1045

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

redis底层数据结构 -String

redis包含5种常用数据结构 String 、List、Hash、Set 、Zset String 字符串不同的外在形式 以set为例 redis其实可以理解为 K-V数据库,因此对每个键值对都会有一个 dictEntry,里面存储了指向 Key 和 Value 的指针;next 指向下一个 dictEntry,与本 Key-Value 无关 key 可以看出 key不是直接存的字符串,而是一个SDS结构 value value既然不是存的String,也不...

liang只的博客 3860

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Redis中的发布订阅和事务

本文是Redis系列的第七篇文章,了解前面的文章有助于更好的理解本文:1.Linux上安装Redis2.Redis中的五种数据类型简介3.Redis字符串(STRING)介绍4.Redis字符串(STRING)中BIT相关命令5.Redis列表与集合6.Redis散列与有序集合发布订阅redis的发布订阅系统有点类似于我们生活中的电台,电台可以在某一个频率上发送广播,而我们可以接收任何一个频率的广播,Android中的broadcast也和这类似。订阅消息的方式如下:127.0.0.1:6379> SUBS

2401_84049282的博客 1044

对redis的新认知 九种数据类型

90%的人知道Redis 5种最基本的数据结构; 只有不到10%的人知道8种基本数据结构,5种基本+bitmap+GeoHash+HyperLogLog; 只有不到5%的人知道9种基本数据结构,5.0最新版本数据结构Streams; 只有不到1%的人掌握了所有9种基本数据结构以及8种内部编码; 掌握这篇文章的知识点,让你成为面试官眼中Redis方面最靓的仔! 说明:本文基于Redis-3.2.11...

qq_40306091的博客 3301

redis篇(一):redis基本数据结构

redis篇(一):redis基本数据结构redis基本数据结构StringHashListSetZsetBitmapsHyperloglogsStreams redis 后续源码文件名称统一通过()表示,redis底层是C语言,因此.h、.c文件可认为是源码文件 源码版本 redis-6.0.5 redis全称:REmote DIctionary Service 译为远程字典服务 每个KV键值对都存储在dictEntry(dict.h)里面,redis底层是哈希表(hashTable),结构体现在

林伟杰的博客 654

Redis实现源码-字符串基础数据类型分析

Redis并没有直接使用C语言中的字符串类型,而是自己设计了一种数据结构,在Redis中,C语言中的字符串只使用在一些字符串值不会发生修改的地方,比如打印日志。它为什么要这样做?这样做的好处是什么呢?让我们先来了解一下这种被称为Simple dynamic string(SDS)简单动态字符的结构。 ...

qq_40280705的博客 329

Redis系列笔记之 —— 键

单位秒。 设置 key 的过期时间以毫秒计 PEXPIRE key milliseconds 和 EXPIRE 类似 EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 和 EXPIRE 类似 PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timest

KG_181的博客 350

Redis 9种数据结构以及它们的内部编码实现

90%的人知道Redis 5种最基本的数据结构; 只有不到10%的人知道8种基本数据结构,5种基本+bitmap+GeoHash+HyperLogLog; 只有不到5%的人知道9种基本数据结构,5.0最新版本数据结构Streams; 只有不到1%的人掌握了所有9种基本数据结构以及8种内部编码; 掌握这篇文章的知识点,让你成为面试官眼中Redis方面最靓的仔! 说明:本文基于Redis-...

谢谢你,慌乱了我的年华 1926

redis源码--动态字符串

sds要点总结: 1.sds定义为sdshdr中的 buff[]指针, 并向外暴露, 而sdsdhr不对外 2.buff[]还是为以'\0'结尾, 以此兼容c 3.buff[]是个柔性数组, 其主要意义在于: a), 便于内存管理, 可以和sdshdr同时申请内存和释放 b), 便于扩容, 如果使用数组则无法扩容, 如果使用指针则无法进行统一的内存管理 c), 和sdshdr内存连续,...

atarik@163.com 544

深入理解跳跃链表在Redis中的应用

0.前言 前面写了一篇关于跳表基本原理和特性的文章,本次继续介绍跳表的概率平衡和工程实现,跳表在Redis、LevelDB、ES中都有应用,本文以Redis为工程蓝本,分析跳表在Redis中的工程实现。 通过本文你将了解到以下内容: Redis基本的数据类型和底层数据结构 Redis的有序集合的实现方法 Redis的跳表实现细节 1.Redis的数据结构 Redis对外共有约五种...

Z875983491的专栏 376

学 Redis ,至少要看看这篇!7000 字小结

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 8:55 更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2020 超神之路,很肝~...

芋艿V 170
上一篇: 阿里最新SpringBoot原理最佳实践真香!从零到构建完整体系!
下一篇: SpringBoot2源码2-核心启动过程和run方法
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值