文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Redis 为什么不直接使用C语言的 string,而是重新造了个 SDS ?

2024-11-29 21:51

关注

为什么 Redis 的 String 可以存放这么多类型的数据?Redis 底层到底是如何实现 String 的呢?今天我们就来聊一聊。

申明:本文源码基于redis-6.2。

一、String的特性 

String 的特性主要包含下面4点:

二、String常用指令 

String 高频指令如下表:

指令

举例

说明

set

set key value

设置值

get

get key

获取值

getset

getset key

先获取之前的值,然后设置一个新的值

del

del key

删除key

incr

incr key

从0开始自增1

incrby

incrby key n

自增指定的步长

decr

decr key 

自减1

decrby

decrby key n

自减指定的步长

append

append key

追加内容

如下图,展示了 String常用指令:

三、实现原理 

上文介绍了 String数据对象的一些基础知识,接下来进入核心内容:String 的 Redis 底层实现。

1. SDS 结构

Redis 底层是 C语言实现的,但是 Redis 的 String数据对象并没有直接使用 C语言传统的字符串,而是自创了一套 SDS,用于 Redis 默认字符串表示。SDS(simple dynamic string),简单动态字符串。

SDS 的结构定义在 sds.h 文件中,每个 sds.h/sdshdr 结构表示一个 SDS 值,在 Redis 3.2 版本之后,SDS 由一种数据结构变成了 5 种数据结构,如下源码截图:

5 种数据结构存储不同长度的内容,Redis 会根据 SDS 存储的内容长度来选择不同的结构,源码实现对应 sds.c/sdsReqType,截图如下:

为了对 SDS 有一个更好的体感,这里以 sdshdr8 为例,执行指令:SET name Redis

执行上述 set 指令后,值对象对应的 SDS 结构如下图:

SDS 各个属性说明:

从上面 SDS 的结构可以看出,SDS 依然遵循了 C语言中字符串以 \0 结尾的规则, 但是,\0占用的1 个字节空间并没有计算在 SDS 的 len 属性里面。

分析完 SDS 的结构,我们会问,SDS 在 Redis 中是如何存放的呢?

因为 Redis 的数据类型有很多(String、List、Set、Hash等等),不同数据类型会包含相同的元数据,所以值对象并不是直接存储,而是被包装成 redisObject 对象(源码位于 server.h中),其定义如下图:

所以,SDS 在 Redis Server 端的存储如下图:

另外,为了节省内存空间,Redis 还做了如下优化:

下图为 int、embstr 和 raw 这三种编码模式的对比:

如果想查看一个值对象是采用哪种编码模式,可以使用 OBJECT ENCODING((大小写不敏感)命令,下面给了几个示例截图:

到此,SDS 的实现原理分析完成,需要补充的是:Redis 官方为了保证 String 的性能,在 SDS 设计上采用了两个非常优秀的设计:空间预分配 和 惰性空间释放。

2. 空间预分配

在对 SDS 进行修改操作时(追加字符串,拷贝字符串等),通常会调用 sds.c/sdsMakeRoomFor 方法对 SDS 的剩余容量进行检查,如有必要会对 SDS 进行扩容,当计算修改之后字符串(用target_string表示)的目标长度之后分以下几种情况:

(1)剩余的 freespace 足够容纳 target_string 和末尾\0字符,则不作任何操作

(2)剩余的 freespace 不够容纳 target_string 和末尾的\0字符

3. 惰性空间释放

当 SDS 字符串缩短时, 空余出来的空间并不会直接释放,而是会被保留,等待下次再次使用,字符串缩短操作需要更新 sdshdr 头中的 Len 字段以及alloced buffer中的\0字符的位置,如下源码截图,在更新字符串长度的过程中并没有涉及到内存的重分配策略,只是简单的修改sdshdr 头中的 Len 字段。

四、SDS 的缺点 

从上面 SDS 的结构可以看出,SDS 除了存储 String 的内容外,还需要额外的内存空间记录数据长度、空间使用等信息,这个就导致了 SDS 的一个比较大的缺点:占内存。那么有什么更好的数据结构呢?我们下篇文章会进行分析。

不过,计算机领域很多时候都在空间和时间上的一种权衡。而Redis String 这种浪费内存换取读写速度就是一个很好的体现。

五、SDS 与 C字符串比较 

1. 获取字符串长度复杂度

C字符串不记录长度,获取长度必须遍历整个字符串,复杂度为O(N),SDS 在 len 属性中记录了 SDS 本身的长度, 获取 SDS 长度的复杂度为 O(1) ;

2. 缓冲区溢出

C字符串不记录自身的长度,每次增长或缩短一个字符串,都要对底层的字符数组进行一次内存重分配操作。如果在 append 操作之前没有通过内存重分配来扩展底层数据的空间大小,就会产生缓存区溢出;如果进行 trim 操作之后没有通过内存重分配来释放不再使用的空间,就会产生内存泄漏;

SDS 通过未使用空间解除了字符串长度和底层数据长度的关联,3.0版本用 free属性记录未使用空间,3.2版本用 alloc属性记录总的分配字节数量。通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化的空间分配策略,解决了字符串拼接和截取的空间问题;

3. 二进制安全

C 字符串以 \0结尾(即 以 \0判断字符串结束),所以在 C字符串的内容里面不能包含 \0,否则会被认为是字符串结尾,因此,C字符串只能保存文本数据,不能保存像图片这样的二进制数据;

而 SDS 的 API 会以处理二进制的方式来处理存放在 bu f数组里的数据,不会对里面的数据做任何的限制。SDS 使用 len 属性来判断字符串是否结束,而不是空字符。

两者比较归纳如下表:

C字符串

SDS

获取字符串长度复杂度为O(N)

获取字符串长度复杂度为O(1)

API是不安全的,可能会造成缓冲区溢出

API是安全的,不会造成缓冲区溢出

修改字符串长度必然需要内存重分配

修改字符串长度 N次最多需要执行 N次内存重分配

只能保存文本数据

可以保存文本或二进制数据

可以使用所有库中的函数

可以使用一部分库中的函数

六、总结

本文从 Redis的底层 SDS 实现分析了 String 的实现原理,可以说 SDS 是一种很优秀的设计,它即遵循了C语言的部分功能,又规避了 C语言 字符串常见的一些问题,这或许就是 Redis 优秀的一个原因。

另外,SDS 为了保证读写速度,尽管做了很多节省内存的操作(比如:sdshdr8/16/32/64,int/embstr/raw),但是,还在是一定程度上采用空间换时间。

通过 SDS 的设计,我们可以看出:在程序的世界里没有“银弹”,每种数据结构似乎总有其擅长的场景以及不足之处,这也正是各种数据结构百花齐放的原因。

最后,回答文章开头的问题,为什么Redis String可以存放图片,视频?

我们把 SDS的结构抽象如下图,尽管 String也是以 \0结尾,但是,因为 SDS 有 len 属性来记录 String 值的内容长度(used space),所以在获取数据时只需要按照 len 获取内容,而无需遍历 String内容,所以也就不用担心内容中有\0 异常结束String,所以可以存放图片,视频等二进制数据。

来源:猿java内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯