Redis设计与实现之简单的动态

news2024/11/26 11:27:40

目录

一、内部数据结构

二、简单动态字符串

1、sds的用途

实现字符串对象

将sds代替C默认的char*类型

2、Redis中的字符串

sds的实现

3、优化追加操作

4、sds 模块的 API

三、Redis动态字符串的内存分配和释放是如何进行的?

四、Redis动态字符串的扩容策略是什么?

五、Redis动态字符串在存储数据时有什么注意事项?

六、Redis动态字符串与常规字符串的区别是什么?

七、小结


一、内部数据结构

Redis和其他很多 key-value 数据库的不同之处在于, Redis不仅支持简单的字符串键值对,它还提供了一系列数据结构类型值,比如列表、哈希、集合和有序集,并在这些数据结构类型上定义了一套强大的 API。

通过对不同类型的值进行操作, Redis可以很轻易地完成其他只支持字符串键值对的 key-value数据库很难(或者无法)完成的任务。

在 Redis的内部,数据结构类型值由高效的数据结构和算法进行支持,并且在 Redis自身的构建当中,也大量用到了这些数据结构。

这一部分将对 Redis内存所使用的数据结构和算法进行介绍。

二、简单动态字符串

Sds(Simple Dynamic String ,简单动态字符串)是 Redis底层所使用的字符串表示,它被用在几乎所有的 Redis模块中。

本章将对 sds的实现、性能和功能等方面进行介绍,并说明 Redis使用 sds而不是传统 C字符串的原因。

1、sds的用途

Sds在 Redis中的主要作用有以下两个:

1.实现字符串对象( StringObject ) ;

2.在 Redis程序内部用作 char*类型的替代品;

以下两个小节分别对这两种用途进行介绍。

实现字符串对象

Redis是一个键值对数据库( key-value DB ) ,数据库的值可以是字符串、集合、列表等多种类型的对象,而数据库的键则总是字符串对象。

对于那些包含字符串值的字符串对象来说,每个字符串对象都包含一个 sds值。

Note:“包含字符串值的字符串对象”,这种说法初听上去可能会有点奇怪,但是在 Redis中,一个字符串对象除了可以保存字符串值之外,还可以保存 long类型的值,所以为了严谨起见,这里需要强调一下:当字符串对象保存的是字符串时,它包含的才是 sds值,否则的话,它就是一个long类型的值。

举个例子,以下命令创建了一个新的数据库键值对,这个键值对的键和值都是字符串对象,它们都包含一个 sds值:

redis>SET book "Mastering C++ in 21 days "

OK

redis>GET book

"Mastering C++ in 21 days "

以下命令创建了另一个键值对,它的键是字符串对象,而值则是一个集合对象:

redis>SADD nosql "Redis""MongoDB""Neo4j"

(integer) 3

redis>SMEMBERS nosql

1)"Neo4j"

2)"Redis"

3)"MongoDB"

将sds代替C默认的char*类型

因为char*类型的功能单一,抽象层次低,并且不能高效地支持一些 Redis常用的操作(比如追加操作和长度计算操作) ,所以在 Redis程序内部,绝大部分情况下都会使用 sds而不是char*来表示字符串。

性能问题在稍后介绍 sds定义的时候就会说到,因为我们还没有了解过 Redis的其他功能模块,所以也没办法详细地举例说那里用到了 sds,不过在后面的章节中,我们会经常看到其他模块(几乎每一个)都用到了 sds类型值。

目前来说,只要记住这样一个事实即可:在 Redis中,客户端传入服务器的协议内容、 aof缓存、返回给客户端的回复,等等,这些重要的内容都是由都是由 sds类型来保存的。

2、Redis中的字符串

在 C语言中,字符串可以用一个 \0结尾的char数组来表示。

比如说, hello world 在 C语言中就可以表示为 "hello world\0" 。这种简单的字符串表示在大多数情况下都能满足要求,但是,它并不能高效地支持长度计算和追加( append)这两种操作:

•每次计算字符串长度( strlen(s) )的复杂度为 (N)。

•对字符串进行 N次追加,必定需要对字符串进行 N次内存重分配( realloc) 。

在 Redis内部,字符串的追加和长度计算并不少见,而 APPEND 和 STRLEN 更是这两种操作在 Redis命令中的直接映射,这两个简单的操作不应该成为性能的瓶颈。

另外, Redis除了处理 C字符串之外,还需要处理单纯的字节数组,以及服务器协议等内容,所以为了方便起见, Redis的字符串表示还应该是 二进制安全的 :程序不应对字符串里面保存的数据做任何假设,数据可以是以 \0结尾的 C字符串,也可以是单纯的字节数组,或者其他格式的数据。

考虑到这两个原因, Redis使用 sds类型替换了 C语言的默认字符串表示: sds既可以高效地实现追加和长度计算,并且它还是二进制安全的。

sds的实现

在前面的内容中,我们一直将 sds作为一种抽象数据结构来说明,实际上,它的实现由以下两部分组成:

typedef char*sds;

structsdshdr {

// buf已占用长度

intlen;

// buf剩余可用长度

intfree;

//实际保存字符串数据的地方

charbuf[];

};

其中,类型 sds是char *的别名 (alias),而结构 sdshdr则保存了 len、free和buf三个属性。

作为例子,以下是新创建的,同样保存 hello world 字符串的 sdshdr结构:

structsdshdr {

len=11;

free=0;

buf="hello world \0";// buf的实际长度为 len + 1

};

通过len属性,sdshdr可以实现复杂度为 (1)的长度计算操作。

另一方面,通过对 buf分配一些额外的空间,并使用 free记录未使用空间的大小, sdshdr可以让执行追加操作所需的内存重分配次数大大减少,下一节我们就会来详细讨论这一点。

当然, sds也对操作的正确实现提出了要求——所有处理 sdshdr的函数,都必须正确地更新len和free属性,否则就会造成 bug。

3、优化追加操作

在前面说到过,利用 sdshdr结构,除了可以用 (1)复杂度获取字符串的长度之外,还可以减少追加 (append) 操作所需的内存重分配次数,以下就来详细解释这个优化的原理。

为了易于理解,我们用一个 Redis执行实例作为例子,解释一下,当执行以下代码时, Redis内部发生了什么:

redis>SET msg "hello world "

OK

redis>APPEND msg "again!"

(integer) 18

redis>GET msg

"hello world again! "

首先,SET命令创建并保存 hello world 到一个sdshdr中,这个 sdshdr的值如下:

structsdshdr {

len=11;

free=0;

}

当执行 APPEND 命令时,相应的 sdshdr被更新,字符串 " again!" 会被追加到原来的"hello world" 之后:

struct sdshdr { 
    len = 18;
    free = 18;
    // 空白的地方为预分配空间,共 18 + 18 + 1 个字节 
    buf = "hello world again!\0 ";
}

注意,当调用 SET 命令创建 sdshdr 时,sdshdr 的 free 属性为 0 ,Redis 也没有为 buf 创建 额外的空间——而在执行 APPEND 之后,Redis 为 buf 创建了多于所需空间一倍的大小。

在这个例子中,保存 "hello world again!" 共需要 18 + 1 个字节,但程序却为我们分配了 18 + 18 + 1 = 37 个字节——这样一来,如果将来再次对同一个 sdshdr 进行追加操作,只要 追加内容的长度不超过 free 属性的值,那么就不需要对 buf 进行内存重分配。

比如说,执行以下命令并不会引起 buf 的内存重分配,因为新追加的字符串长度小于 18 :

redis> APPEND msg " again!"
(integer) 25

 再次执行 APPEND 命令之后,msg 的值所对应的 sdshdr 结构可以表示如下:

struct sdshdr { 
    len = 25;
    free = 11;
    // 空白的地方为预分配空间,共 18 + 18 + 1 个字节
    buf = "hello world again! again!\0 ";
}

sds.c/sdsMakeRoomFor 函数描述了 sdshdr 的这种内存预分配优化策略,以下是这个函数的 伪代码版本:

def sdsMakeRoomFor(sdshdr, required_len):
    # 预分配空间足够,无须再进行空间分配 
    if (sdshdr.free >= required_len):
        return sdshdr
# 计算新字符串的总长度
newlen = sdshdr.len + required_len
# 如果新字符串的总长度小于 SDS_MAX_PREALLOC
# 那么为字符串分配 2 倍于所需长度的空间
# 否则就分配所需长度加上 SDS_MAX_PREALLOC 数量的空间 
if newlen < SDS_MAX_PREALLOC:
    newlen *= 2 
else:
    newlen += SDS_MAX_PREALLOC 
# 分配内存
newsh = zrelloc(sdshdr, sizeof(struct sdshdr)+newlen+1) 
# 更新 free 属性
newsh.free = newlen - sdshdr.len
# 返回 
return newsh

在目前版本的 Redis 中,SDS_MAX_PREALLOC 的值为 1024 * 1024 ,也就是说,当大小小于 1MB 的字符串执行追加操作时,sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间;当 字符串的大小大于 1MB ,那么 sdsMakeRoomFor 就为它们额外多分配 1MB 的空间。

Note: 这种分配策略会浪费内存吗?

执行过 APPEND 命令的字符串会带有额外的预分配空间,这些预分配空间不会被释放,除非 该字符串所对应的键被删除,或者等到关闭 Redis 之后,再次启动时重新载入的字符串对象将 不会有预分配空间。

因为执行 APPEND 命令的字符串键数量通常并不多,占用内存的体积通常也不大,所以这一 般并不算什么问题。

另一方面,如果执行 APPEND 操作的键很多,而字符串的体积又很大的话,那可能就需要修 改 Redis 服务器,让它定时释放一些字符串键的预分配空间,从而更有效地使用内存。

4、sds 模块的 API

sds 模块基于 sds 类型和 sdshdr 结构提供了以下 API :

三、Redis动态字符串的内存分配和释放是如何进行的?

Redis中的动态字符串类型redisObject内部实现了引用计数和惰性释放机制来管理内存分配和释放。

  1. 内存分配:Redis使用malloc函数分配内存块来存储动态字符串。动态字符串结构体包含了当前字符串长度、可用空间大小和字符数组,其中字符数组的长度至少是当前字符串长度加一。

  2. 引用计数:每个redisObject对象都包含一个refcount字段,记录了指向该对象的指针数量。当其他对象引用一个动态字符串时,它们会增加该动态字符串的引用计数。

  3. 惰性释放:当一个动态字符串不再被引用时,其引用计数会减少。如果引用计数变为0,Redis会将其标记为可释放状态,并不立即释放其内存。而是在合适的时机,例如内存紧张或者执行一次RDB或AOF持久化操作时,Redis会对所有可释放的动态字符串进行释放。

通过引用计数和惰性释放的组合,Redis可以减少内存分配和释放的次数,提高性能。同时,惰性释放机制也能够在需要释放内存时,一次性释放多个动态字符串,减少系统调用的开销。

四、Redis动态字符串的扩容策略是什么?

Redis动态字符串的扩容策略是通过预分配的方式来保证字符串的扩容操作效率。具体策略如下:

  1. 增长策略:当字符串长度增长时,Redis会根据当前字符串长度选择适当的增长策略。

    • 如果当前字符串长度小于1MB,每次增长会以当前长度的2倍来进行。
    • 如果当前字符串长度大于等于1MB,每次增长会增加1MB的长度。
  2. 预分配策略:Redis会预分配更大的空间,在每次扩容时,会为字符串分配比当前长度更大的空间。这样可以减少频繁地进行内存重新分配的次数,提高性能。

  3. 惰性空间释放策略:当字符串长度缩小时,Redis并不会立即释放多余的空间,而是将其保留起来。这样可以避免频繁地进行内存分配和释放操作,提高性能。

需要注意的是,Redis动态字符串是通过结构体sds(Simple Dynamic Strings)来实现的,结构体中记录了字符串的长度和剩余空间等信息。扩容策略可以保证字符串长度的增长和缩小的效率,同时避免了频繁进行内存分配和释放的操作。

五、Redis动态字符串在存储数据时有什么注意事项?

在存储数据时,使用Redis的动态字符串需要注意以下事项:

  1. 动态字符串可以存储任意的二进制数据,但需要注意数据的大小限制。Redis的最大字符串长度是512MB,如果要存储较大的数据,需要进行分片或者压缩等处理。

  2. 动态字符串是可修改的,但是在修改字符串时需要注意原来字符串的引用计数。如果有其他地方引用了该字符串,修改时需要先拷贝一份副本,再进行修改。

  3. 当动态字符串的长度超过预分配的空间时,需要进行重新分配和拷贝。这个过程可能会引发内存分配和拷贝的开销,因此在使用动态字符串时需要合理预估字符串的最大长度。

  4. 如果需要频繁地对字符串进行拼接、截取等操作,使用动态字符串性能更高。因为动态字符串的底层实现是一个char数组,可以直接通过索引访问和修改。

总而言之,使用动态字符串在存储数据时需要注意数据大小限制、引用计数、内存分配等问题,以及合理预估字符串的最大长度。

六、Redis动态字符串与常规字符串的区别是什么?

Redis的动态字符串与常规字符串有以下区别:

  1. 内存预分配:Redis的动态字符串会根据字符串的长度进行内存预分配,以减少字符串长度改变时的内存重新分配的次数。而常规字符串则需要每次长度改变都进行内存重新分配。

  2. 缓冲区溢出:Redis的动态字符串会维护一个字节缓冲区,用于保存字符串的内容。当字符串的长度超过缓冲区的大小时,会自动扩展缓冲区的大小,避免缓冲区溢出。而常规字符串没有这样的缓冲区,当字符串的长度超过字符串本身分配的空间时,会导致缓冲区溢出。

  3. 修改操作效率:Redis的动态字符串通过修改字符串结构体的方式进行字符串操作,可以在O(1)的时间内进行字符串长度的修改,并且不需要进行内存拷贝。而常规字符串需要在O(N)的时间内进行字符串长度的修改,并且需要进行内存拷贝。

  4. 字符串共享:Redis的动态字符串支持字符串共享,即多个键值对可以共享相同的字符串内容,节约内存。而常规字符串不支持字符串共享,每个字符串在内存中都有一份独立的拷贝。

总的来说,Redis的动态字符串相比常规字符串在内存管理、性能和内存占用等方面有一定的优势。

七、小结

• Redis 的字符串表示为 sds ,而不是 C 字符串(以 \0 结尾的 char*)。

• 对比 C 字符串,sds 有以下特性:

        – 可以高效地执行长度计算(strlen);

         – 可以高效地执行追加操作(append);

        – 二进制安全;

• sds 会为追加操作进行优化:加快追加操作的速度,并降低内存分配的次数,代价是多占 用了一些内存,而且这些内存不会被主动释放。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1311642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css+js 选项卡动画效果

选项卡上下左右翻转动画效果 <template><div class"web-box"><div class"topTitle"><div class"topTitle1">标题标题</div></div><div class"info-wrap"><div style"width: 100%;h…

互联网加竞赛 python 机器视觉 车牌识别 - opencv 深度学习 机器学习

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于python 机器视觉 的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;3分 &#x1f9ff; 更多资…

掌握iText:轻松处理PDF文档-高级篇-添加页眉和页脚

推荐语 本文介绍了如何使用iText编程库为PDF文档添加自定义的页眉和页脚。通过指定位置、大小、字体和颜色等属性&#xff0c;你可以将文本、图像或其他元素添加到每一页的固定位置&#xff0c;实现专业、可读的自定义页眉和页脚效果。这对于需要批量处理大量PDF文档或需要更精…

基于轻量级GhostNet模型开发构建工业生产制造场景下滚珠丝杠传动表面缺陷图像识别系统

轻量级识别模型在我们前面的博文中已经有过很多实践了&#xff0c;感兴趣的话可以自行移步阅读&#xff1a; 《移动端轻量级模型开发谁更胜一筹&#xff0c;efficientnet、mobilenetv2、mobilenetv3、ghostnet、mnasnet、shufflenetv2驾驶危险行为识别模型对比开发测试》 《基…

YOLOv5改进 | 2023卷积篇 | AKConv轻量级架构下的高效检测(既轻量又提点)

一、本文介绍 本文给大家带来的改进内容是AKConv是一种创新的变核卷积&#xff0c;它旨在解决标准卷积操作中的固有缺陷&#xff08;采样形状是固定的&#xff09;&#xff0c;AKConv的核心思想在于它为卷积核提供了任意数量的参数和任意采样形状&#xff0c;能够使用任意数量…

***Cpolar配置外网访问和Dashy

Dashy是一个开源的自托管的导航页配置服务,具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起,形成自己的导航页。一款功能超强大,颜值爆表的可定制专属导航页工具 结合cpolar内网工具,我们实现无需部署到公网服务器…

Spring Boot 3 整合 WebSocket (STOMP协议) 和 Vue 3 实现实时通信

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

基于 Webpack5 Module Federation 的业务解耦实践

前言 本文中会提到很多目前数栈中使用的特定名词&#xff0c;统一做下解释描述 dt-common&#xff1a;每个子产品都会引入的公共包(类似 NPM 包) AppMenus&#xff1a;在子产品中快速进入到其他子产品的导航栏&#xff0c;统一维护在 dt-common 中&#xff0c;子产品从 dt-com…

【Https】工作流程

HTTPS 也是⼀个应用层协议。是在 HTTP 协议的基础上引入了⼀个加密层。 前言 由于Http是明文传输&#xff0c;因此如果有人想修改/截获数据都是非常容易&#xff0c;因此就出现了运营商劫持问题。 加密基础知识 明文密钥>密文 加密 密文密钥>明文 解密 对称加密和非对…

亚马逊云科技 re:Invent 大会 - ElastiCache Serverless模式来袭

亚马逊云科技 re:Invent 大会 - ElastiCache Serverless模式来袭 本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道。 文章目录 亚马逊云…

jenkins 运行接口自动化测试脚本,安装第三方依赖库的一些总结

之前在做接口自动化时&#xff0c;jenkins 执行 job 安装 requirements.txt 中的第三方依赖库时折腾了许久&#xff0c;网上查的解决方案均未生效&#xff0c;后来找出一条解决方法&#xff0c;做个记录&#xff0c;希望帮助到遇到同样问题的小伙伴。 我们都知道可以通过生成 …

软考机考考试第一批经验分享

由于机考的特殊性&#xff0c;考试环境与传统笔试环境有所不同。下面是与考试环境相关的总结&#xff1a; 草稿纸&#xff1a;考场提供足够数量的草稿纸&#xff0c;每位考生都会分发一张白纸作为草稿纸。在草稿纸上需要写上准考证号。如果不够用&#xff0c;可以向监考老师再次…

Android 11.0 framework关于systemUI状态栏透明背景的功能实现

1.概述 在11.0的系统rom产品定制化开发中,在对于系统原生SystemUI的状态栏背景在沉浸式状态栏的 情况下默认是会随着背景颜色的变化而改变的,在一些特定背景下状态栏的背景也是会改变的,所以由于产品开发需要 要求需要设置状态栏背景为透明的,所以就需要在Activity创建的时…

排序算法(二)-冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序

排序算法(二) 前面介绍了排序算法的时间复杂度和空间复杂数据结构与算法—排序算法&#xff08;一&#xff09;时间复杂度和空间复杂度介绍-CSDN博客&#xff0c;这次介绍各种排序算法——冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序。 文章目录 排…

web前端之正弦波浪动功能、repeat、calc

MENU 效果图htmlstylecalcrepeat 效果图 html <div class"grid"><span class"line"></span><span class"line"></span><span class"line"></span><span class"line"><…

Python采集知乎专栏文章保存成pdf

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: Python 3.8 Pycharm wkhtmltopdf 软件 – 文末获取 模块使用: requests >>> pip install requests 数据请求 parsel >>> pip install parsel 数据解析 re >>> 内置模块 不需要安装…

linux 防火墙systemctl (个人笔记)

查看 systemctl status firewalld 开启 systemctl start firewalld 关闭 systemctl stop firewalld.service 查看所有 firewall-cmd --zonepublic --list-ports 开放端口&#xff1a;// --permanent 永久生效,没有此参数重启后失效 firewall-cmd --zonepublic --add-port9527/…

[PyTorch][chapter 7][李宏毅深度学习][深度学习简介]

前言&#xff1a; 深度学习常用的开发平台 TensorFlow torch theano caffe DSSTNE mxnet libdnn CNTK 目录&#xff1a; 1&#xff1a; 深度学习发展历史 2&#xff1a; DeepLearning 工程简介 3&#xff1a; DNN 简介 一 发展历史 二 DeepLearning 工程简介 深度学习三…

如何删除/替换3D模型的材质贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 在3D设计和动画领域&#xff0c;材质是呈现真实感和逼真效果的关键因…

从广东到俄罗斯:一段跨越万里的电子消费展之旅

作为一名广东电子消费品行业的从业者&#xff0c;我们经常要奔赴全球不同国家拓展海外业务&#xff0c;而展会就是重要的平台。最近大家在热议的俄罗斯国际消费类电子电器展我参加过一届&#xff0c;跨越万里的旅程&#xff0c;让我深刻体会到了这种国际展览的魅力和挑战。 我参…