Redis系列之——这样的String你肯定不知道
mhr18 2024-11-21 17:53 22 浏览 0 评论
Redis系列之——这样的String你肯定不知道!
前言
在上一篇文章中,我和大家介绍了Redis的前世今生,Redis的诞生就是为了解决mysql中IO性能的瓶颈,这一篇就和大家一起揭秘Redis神秘的面纱,第一个我们就来聊一聊Redis数据类型中的String!
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'作为识别,这样就会带来以下问题 无法存放任意的字符,至少'\0'是不可以的,这就会导致一些如图片,音频等出现了'\0'就会出现问题。对字符串进行追加等操作的时候,必须遍历到'\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个特性就是空间预分配 和 惰性空间释放
空间预分配
空间预分配源码
在真正执行拼接等操作之前,会先计算下空间是否满足
- 如果计算后的SDS的长度(源码中的newLen)小于1MB,那么就会分配和len属性同样大的未使用空间,相当于将原数组长度乘以二。
- 如果计算后的SDS的长度(源码中的newLen)大于或等于1MB,那么就会分配固定的1MB未使用空间。
所以当有N次拼接操作时候,一定需要N次的内存重分配操作,现在最多只需要N次。
比如你第一次拼接后,剩余的长度(alloc - len)就大于后续要拼接的字符串长度之和了,那其实就只有1次内存重分配操作,就可以进行至少2次拼接操作,所以说最多N次,这也就可以省下很多分配空间的消耗。
惰性空间释放
当有缩短SDS字符串操作时,程序并不立即把空闲出来的字节释放掉,而是留给到惰性删除策略等机制释放。
具体的表现就是:
在做删除操作的时候,当删掉N个字符后,alloc-len(也就是剩余空间)就增加N,换个说法,就是你删除的字符并没有立即被系统回收,这样当你短时间内再次拼接的时候,就不会申请空间分配了。
只有到了对应策略或者手动去删除的时候才会执行以下的代码:
代码的逻辑就是删除多余的空间直到没有浪费为止。
有了SDS,就万事大吉了么?
前面我们聊完了SDS,已经大概的知道了SDS是什么样的,解决了什么问题,这里给大家提出一些更深层的问题:
- 如果我的string是数字,Redis也按照字符串存储么?
- string在Redis中就是只有SDS就可以落地了么,有没有别的结构支撑?
- 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的部分源码
这里做的事情核心
- 先分配一段连续的内存空间(RedisObject+SDS+1的长度,这里的1是为了字符串最后的`'\0'),并创建RedisObject和SDS的机构。
- 将RedisObject的ptr指针指向字符数组的开始。(细节!!!!)
- 拷贝字符串到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件事:
- 先使用sdsnewlen返回SDS结构的指针
- 将SDS指针赋值给RedisObject中的指针
最后再给大家把所有的编码格式放一起汇总一下:
课后思考
Redis关于String的介绍已经结束了,但有个问题我还没给大家解释,这个问题大家可以思考一下,欢迎在评论区讨论你的答案或者联系我聊一聊,下期会为大家解答!
问题:embstr编码方式和raw编码方式的转折点为什么是44字节?
更多干货文章,点击这里可以查看-->
作者介绍
- 某大厂高级软件工程师,代码爱好者
- 定期分享技术相关,热点实时,计算机实用技巧
- 有问题可在评论区回复或者联系我,用专业,程序员的思维带给你不一样的认知
更多知识分享,欢迎关注点赞收藏评论~
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)