【高阶篇】3.3 Redis之底层数据结构简单动态字符串(SDS)详解


redis高阶篇.jpg

👏 简介:大家好,我是冰点,从业11年,目前在物流独角兽企业从事技术管理和架构设计方面工作,之前的把博客作为技术流水账在写。现在准备把多年的积累整理一下,成体系的分享给大家,也算是对多年开发生涯的总结。如果你在工作和学习中遇到问题也可反馈给我(iceicepip), 路漫漫其修远兮,吾将上下而求索。
 
🏳️‍🌈2023计划:
             🚩1. 将多年来整理的Redis学习和实践笔记整理并发布成专栏。
             🚩2. 将最近2年在groovy实践应用上的沉淀的初稿,发布成书籍。
             🚩3. 将多年来整理的MySQL学习研究笔记整理并发布成专栏。
             🚩4. 根据技术交流群答疑的问题,整理成博客文章发布分享。

我陆续将我整理的Redis精选比较,分享给大家,今天分享的是Redis底层数据结构简单动态字符串(SDS)。我们从数据结构解析,包括Redis5和Redis6的SDS结构差异分析。这样设计的优势,如果有不对的地方,请大家指正。

Redis是一个快速、开源、内存数据库,它是一个基于键值对的存储系统,由Salvatore Sanfilippo开发。Redis支持多种数据结构,其中字符串(String)是最常用的一种数据结构之一。在Redis中,字符串是由简单动态字符串(SDS)实现的。本文将详细介绍SDS的内部实现原理、优势以及在Redis中的应用。图片来源网络
image.png
redis底层数据结构

1.原理解析

1.1.SDS的内部实现原理

image.png
redis6.x版本底层数据结构

1.1.1 Redis 6.0版本和Redis5.0对比

Redis 6.0版本中,对于SDS的底层数据结构进行了升级。除了原有的SDS类之外,还有四个新的类:sdshdr8、sdshdr16、sdshdr32、sdshdr64。这些类的命名与其中的成员变量类型相关,分别表示使用8位、16位、32位、64位的无符号整数存储字符串长度和容量。
image.png

1.1.2 redis6和redis5对比

Redis 6.0版本相比Redis 5.0版本在SDS底层数据结构上进行了一些改进和优化。Redis 6.0中的SDS仍然包含三个成员变量lenfreebuf,但是buf不再是一个字符数组,而是一个unsigned char类型的数组。此外,在Redis 6.0中新增了四个SDS类:sdsHdr5sdsHdr8sdsHdr16sdsHdr32。这四个类分别代表SDS字符串的头部数据结构,用于存储SDS字符串的长度和空闲空间,以及标记SDS字符串的类型。

Redis 6.0版本中的这些改进和优化可以提高SDS的效率和灵活性。通过使用unsigned char类型的数组来存储SDS字符串,可以更好地处理二进制数据和字符编码。而新增的四个SDS类可以更灵活地处理不同长度的SDS字符串,减少内存浪费。此外,为了提高效率,Redis 6.0版本中还对SDS的内存管理进行了优化,避免了频繁的内存分配和释放操作。

这样的设计可以根据实际情况选择更合适的底层数据结构,从而减少内存占用。例如,当存储的字符串长度较小时,可以选择使用sdshdr8或sdshdr16,而当存储的字符串长度较大时,则可以使用sdshdr32或sdshdr64

1.1.3 优势

上面我们已经解析了一下Redis5和Redis6的数据结构,我们可以总结归纳出SDS具有以下优点,以下这些优点,我也不再赘述。根据上面的解析我们很容易理解。

1.1.3.1. 动态扩容

SDS可以动态增加内存空间,避免了静态数组的大小限制。

1.1.3.2. 常数复杂度获取字符串长度

SDS中的len属性表示字符串长度,可以在常数时间内获取字符串长度。

1.1.3.3. 杜绝缓冲区溢出

SDS会检查内存是否足够,避免了缓冲区溢出的问题。

1.1.3.4. 减少修改字符串的内存重新分配次数

SDS采用惰性空间释放和空间预分配的策略,可以减少修改字符串的内存重新分配次数。

1.1.3.5. 二进制安全

SDS不以空字符串来判断字符串是否结束,而是以len属性表示的长度来判断字符串是否结束,所以支持存储任何二进制数据。
关于二进制安全我引用一个网友的图片更形象
C语言是判断空字符(‘\0’)去判断一个字符的长度的,但是有很多数据结构经常会穿插空字符在中间,比如图片,音频,视频,压缩文件的二进制数据,就比如下面这个单词,只能识别前面的 不能识别后面的字符。
image.png

Redis就不存在这个问题了,他不是保存了字符串的长度嘛,他不判断空字符,只判断长度,所以redis也经常被拿来保存各种二进制数据

1.1.3.6. 兼容部分C字符串函数

SDS可以重用C语言库<string.h>中的一部分函数。
SDS的实现位于Redis的源代码中的src/sds.h和src/sds.c中。SDS的总体结构包括头部sdshdr和存储用户数据的buf,其中用户数据后总跟着一个\0。SDS有四种不同的头部,分别是sdshdr8、sdshdr16、sdshdr32和sdshdr64,其中len属性表示字符串长度,buf[]数组用来保存字符串的每个元素,alloc属性表示整个SDS除过头部与末尾的\0,剩余的字节数,flags始终为一字节,以低三位标识着头部的类型,高5位未使用。

下面是SDS的头部结构体示例:

struct sdshdr {
    uint32_t len;    //字符串长度
    uint32_t alloc;  //字符串空间大小
    unsigned char flags; //表示sds的类型(8位)
    char buf[];  //用于存储字符串数据
};

image.png

SDS的头部结构体中,len和alloc是SDS的关键属性,它们分别表示字符串的长度和分配的空间大小。对于一个SDS字符串,其实际长度可以通过len属性来获取,而其当前分配的空间大小则可以通过alloc属性来获取。
image.png

SDS支持的最大长度是2^32字节,因此len和alloc都是32位无符号整数。SDS的len属性不仅用于表示字符串的长度,还用于记录buf数组中已使用的字节数,因此SDS的实际长度可能比len属性的值要小。

SDS的优点在于它可以避免C字符串常见的问题,比如缓冲区溢出和内存泄露等。SDS的常数复杂度获取字符串长度和杜绝缓冲区溢出可以避免使用strlen和strcat函数时的一些问题。同时,SDS的空间预分配和惰性空间释放两种策略可以减少修改字符串的内存重新分配次数。SDS也是二进制安全的,因为它不是以空字符串来判断字符串是否结束,而是以len属性表示的长度来判断字符串是否结束。SDS还兼容部分C字符串函数,可以重用C语言库<string.h>中的一部分函数。

2. SDS在Redis中的应用

SDS是Redis中最常用的底层数据结构之一,它被广泛应用于各种场景中,比如缓存、计数器、分布式锁等。在Redis中,SDS不仅作为字符串类型的基础实现,还被应用于其他数据结构中。

2.1. 字符串类型

在Redis中,字符串类型是最常用的数据结构之一,它可以用于存储各种类型的数据,比如整数、浮点数、二进制数据等。字符串类型中的字符串值是由SDS实现的,它可以动态扩容,避免了静态数组的大小限制。SDS还可以减少修改字符串的内存重新分配次数,从而提高了性能。

SDS在Redis中的应用非常广泛,它作为字符串类型的基础实现,还被应用于其他数据结构中。SDS的优点在于它可以避免C字符串常见的问题,比如缓冲区溢出和内存泄露等。SDS的常数复杂度获取字符串长度和杜绝缓冲区溢出可以避免使用strlen和strcat函数时的一些问题。同时,SDS的空间预分配和惰性空间释放两种策略可以减少修改字符串的内存重新分配次数。SDS也是二进制安全的,因为它不是以空字符串来判断字符串是否结束,而是以len属性表示的长度来判断字符串是否结束。SDS还兼容部分C字符串函数,可以重用C语言库<string.h>中的一部分函数。

2.2.杜绝缓冲区溢出

字符串拼接是我们经常做的操作,在C和Redis中一样,也是很常见的操作,但是问题就来了,C是不记录字符串长度的,一旦我们调用了拼接的函数,如果没有提前计算好内存,是会产生缓存区溢出的。

比如本来字符串长这样:
image.png

现在需要在后面拼接 ,但是没计算好内存,结果就可能这样了:

image.png

3. SDS的优化技巧

在使用SDS时,还有一些优化技巧可以提高性能:

3.1. 尽量避免频繁修改SDS的值

SDS的修改操作会引起内存重新分配,因此频繁修改SDS的值会导致性能下降。如果需要频繁修改SDS的值,可以考虑使用缓存等技术来避免频繁的修改操作。

3.2. 使用API操作SDS

SDS提供了一些API操作,比如sdscat、sdscmp、sdsnew等,使用这些API操作可以避免直接操作SDS的buf数组,从而提高代码的可读性和可维护性。

3.3. 避免使用大量的短字符串

空间预分配策略会分配一定的额外空间,用于存储未来可能的扩展。如果使用大量的短字符串,会浪费SDS的空间预分配策略,因为大量的短字符串可能占用预分配的空间,而未来可能需要更多的空间来存储更长的字符串。因此,如果需要存储大量的短字符串,可以考虑使用其他数据结构,比如哈希表或者列表。

3.4. 避免使用过大的SDS

最大长度是2^32字节,因此如果需要存储非常大的字符串,可以考虑使用其他的存储方式,比如文件系统或者分布式存储系统。

3.5. 使用SDS的优点

SDS的优点在于它可以避免C字符串常见的问题,比如缓冲区溢出和内存泄露等。SDS的常数复杂度获取字符串长度和杜绝缓冲区溢出可以避免使用strlen和strcat函数时的一些问题。同时,SDS的空间预分配和惰性空间释放两种策略可以减少修改字符串的内存重新分配次数。SDS也是二进制安全的,因为它不是以空字符串来判断字符串是否结束,而是以len属性表示的长度来判断字符串是否结束。SDS还兼容部分C字符串函数,可以重用C语言库<string.h>中的一部分函数。

4.总结

SDS是Redis中的一种字符串类型,它是一种二进制安全的字符串,由简单动态字符串(SDS)实现。SDS支持多种数据结构,其中字符串(String)是最常用的一种数据结构之一。SDS的优点在于它可以避免C字符串常见的问题,比如缓冲区溢出和内存泄露等。SDS的常数复杂度获取字符串长度和杜绝缓冲区溢出可以避免使用strlen和strcat函数时的一些问题。同时,SDS的空间预分配和惰性空间释放两种策略可以减少修改字符串的内存重新分配次数。SDS也是二进制安全的,因为它不是以空字符串来判断字符串是否结束,而是以len属性表示的长度来判断字符串是否结束。SDS还兼容部分C字符串函数,可以重用C语言库<string.h>中的一部分函数。

在Redis中,SDS不仅作为字符串类型的基础实现,还被应用于其他数据结构中。使用SDS时,可以避免频繁修改SDS的值,使用API操作SDS,避免使用大量的短字符串,避免使用过大的SDS,充分利用SDS的优点,提高代码的性能和可读性。

5. 系列文章

《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
在这里插入图片描述
参考资料:

  1. 陈雷老师的《Redis5设计与源码分析 》
  2. Redis 6.0源码
SDS简单动态字符串)是Redis底层数据结构之一。它采用一段连续的内存空间来存储字符串,并具有一定的灵活性和扩展性。下面是一个展示SDS数据结构的例子: ``` typedef struct sdshdr { int len; // 已占用空间的长度 int free; // 剩余可用空间的长度 char buf[]; // 字符数组 } sdshdr; ``` 在这个例子中,我们可以看到SDS结构包含了`len`表示已占用空间的长度,`free`表示剩余可用空间的长度,以及`buf[]`表示字符数组。举一个实际的例子,如果我们要存储字符串"Redis",SDS会为其分配5字节的已使用长度,并为其分配5字节的可用空间长度。 与C字符串相比,SDS具有一些区别。C字符串的长度需要通过遍历整个字符串才能获得,而SDS可以直接通过`len`属性来获取字符串的长度。此外,C字符串是以空字符'\0'作为字符串的结束标志,而SDS通过`len`属性来确定字符串的长度,不依赖空字符。 在Redis中,C字符串主要应用在不需要对字符串值进行修改的地方,比如打印日志。而SDS主要应用在需要修改字符串值的地方,比如Redis数据库中,包含字符串值的键值对在底层都是使用SDS来实现的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis底层数据结构——SDS](https://blog.csdn.net/weixin_39939725/article/details/110912176)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰点.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值