首页 > 数据库 >Redis设计与实现(一)SDS与C字符串的对比

Redis设计与实现(一)SDS与C字符串的对比

时间:2024-06-02 18:28:52浏览次数:52  
标签:SDS Redis 空间 API 内存 字符串 分配

sds的定义:

每个sds.h/sdshdr结构表示一个SDS值:

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
  1. len 表示 sds 结构中已经被使用的字节数,也就是字符串的长度;
  2. alloc 表示 sds 结构中分配的总字节数,是已有内容和冗余空间的总和;
  3. flags 是一个单字节的字段,使用的只有最低的 3 位,用来表示 sds 头是上面四种中的哪一种;

可以看到,这四种的整体结构是很像的,区别只是 len alloc 的取值范围不同,意即它们能容纳的 sds 字节数不同。

SDS遵循C字符串以空字符结尾的惯例,好处是SDS可以直接重用一部分C字符串函数库里面的函数。

举个例子:

打印出SDS保存的字符串值,而无须为SDS编写专门的打印函数。

printf("%s", s->buf);

SDS与C字符串对比

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

C字符串并不记录自身的长度信息,所以获取一个C字符串的长度,必须遍历整个字符串,对每个字符进行计数,直到遇到空字符为止,这个操作的复杂度为O(N)。

和C字符串不同,因为SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度仅为O(1)。

设置和更新SDS长度(len)的工作是由SDS的API在执行时自动完成的,使用SDS无须进行任何手动修改长度的工作。

2.杜绝缓冲区溢出:

char *strcat(char *dest, const char *src)

因为C字符串不记录自身的长度,所以strcat假定用户在执行这个函数时,已经为dest分配了足够多的内存,可以容纳src字符串中的所有内容,而一旦这个假定不成立时,就会产生缓冲区溢出。

举个例子,假设程序里有两个在内存中紧邻着的C字符串s1和s2,其中s1保存了字符串"Redis",而s2则保存了字符串"MongoDB",如果程序员执行

strcat(s1, "cluster");

将s1的内容修改为"Redis Cluster",但在执行strcat之前没有为s1分配足够的空间

那么在strcat函数执行之后,s1的数据将溢出到s2所在的空间中,导致s2保存的内容被意外地修改。

与C字符串不同,当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用SDS既不需要手动修改SDS的空间大小,也不会出现前面所说的缓冲区溢出问题。

举个例子,SDS的API里面也有一个用于执行拼接操作的sdscat函数,它可以将一个C字符串拼接到给定SDS所保存的字符串的后面,但是在执行拼接操作之前,sdscat会先检查给定SDS的空间是否足够,如果不够的话,sdscat就会先扩展SDS的空间,然后才执行拼接操作。

3.减少修改字符串时带来的内存重分配次数:

C字符串每次增长或者缩短一个C字符串,程序都总要对保存这个C字符串的数组进行一次内存重分配操作

  • 如果忘记了内存重分配来扩展底层数组的空间大小,就会产生缓冲区溢出。

  • 如果忘记了内存重分配来缩短底层数组的空间大小,就会产生内存泄漏。

因为内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时的操作

Redis作为数据库,经常被用于速度要求严苛、数据被频繁修改的场合,如果每次修改字符串的长度都需要执行一次内存重分配的话,那么光是执行内存重分配的时间就会占去修改字符串所用时间的一大部分,如果这种修改频繁地发生的话,可能还会对性能造成影响。

为了避免C字符串的这种缺陷,SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联

1.空间预分配

空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间

额外分配的未使用空间数量由以下公式决定:

如果对SDS进行修改之后,SDS的长度(也即是len属性的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间。若SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。

在扩展SDS空间之前,SDS API会先检查未使用空间是否足够,如果足够的话,API就会直接使用未使用空间,而无须执行内存重分配。通过这种预分配策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次降低为最多N次。

需要注意的是:

  • 如果结构中自由空间大小大于 addlen,则不会执行扩容,而是直接返回原来的 s;
  • 如果执行了扩容,会返回全新的内存区域,原来的 s 指向的空间会被释放;

2.惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。

举个例子,sdstrim函数接受一个SDS和一个C字符串作为参数,移除SDS中所有在C字符串中出现过的字符。

执行sdstrim之后的SDS并没有释放多出来的8字节空间,而是将这8字节空间作为未使用空间保留在了SDS里面,如果将来要对SDS进行增长操作的话,这些未使用空间就可能会派上用场。

与此同时,SDS也提供了相应的API,让我们可以在有需要时,真正地释放SDS的未使用空间,所以不用担心惰性空间释放策略会造成内存浪费。

3.二进制安全

C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据

而SDS使用len属性的值而不是空字符来判断字符串是否结束

总结:

在这里插入图片描述

标签:SDS,Redis,空间,API,内存,字符串,分配
From: https://blog.csdn.net/m0_74973115/article/details/139395097

相关文章

  • Groovy基础语法-字符串篇
    索引取值str1="devops-test-stings"1、获取字符串倒数第一个的值groovy:000>printlnstr1[-1]s2、获取索引为2的值groovy:000>printlnstr1[2]v3、获取多个下标的值,用“,”号隔开groovy:000>printlnstr1[0,2,4]dvp4、获取字符串第一个到第四个的值,可用于截......
  • Redis单线程
    Redis是基于Reactor模式开发的网络事件处理器,这个处理器是单线程的,所以redis是单线程的。为什么它是单线程还那么快呢?主要有以下几个原因:一、纯内存操作由于Redis是纯内存操作,相比于磁盘来说,内存就快得多,这个是Redis快的主要原因。二、多路复用I/O机制(NIO)Re......
  • Linux系统上配置redis开机自启
    Redis开机自启:第一步添加环境变量:命令:vim/etc/profile在结尾添加:exportPATH=$PATH:/usr/local/redis/bin作用是为了后续脚本的启动命令不需写的过长重载环境变量文件:source/etc/profile第二步:编写redis.service节点1:152服务器vim/etc/systemd/system/redis.service添......
  • Redis的分布式缓存问题
    击穿  Redis曾存在的key,由于过期时间而被删除,导致请求跳过redis而访问DB处理方法:不设置过期时间,永远存在使用锁,synchronized、分布式锁布隆过滤器穿透  数据库与redis都不存在的key,由于莫名原因存在大量请求,导致请求跳过redis而访问DB处理方法:数据库不存在,redis也......
  • Redis 高级应用与性能优化
    目录1.Redis集群与高可用性Redis集群介绍高可用性方案与实践2.Redis性能优化与监控性能指标与监控工具Redis的性能优化策略实时监控与故障排查3.Redis实践场景与最佳实践缓存与缓存雪崩、击穿、穿透计数器和限流器的实现分布式锁的应用实际项目中的Redis......
  • Day 10:100322. 删除星号以后字典序最小的字符串
    Leetcode100322.删除星号以后字典序最小的字符串给你一个字符串s。它可能包含任意数量的‘’字符。你的任务是删除所有的'’字符。当字符串还存在至少一个‘*’字符时,你可以执行以下操作:删除最左边的‘*’字符,同时删除该星号字符左边一个字典序最小的字符......
  • Redis集群搭建实战(主从复制、哨兵、集群)
    目录1、安装Redis3.02、主从复制(读写分离)2.1主从架构2.1.1 启动实例2.1.2设置主从2.1.3测试2.2主从从架构2.2.1启动实例2.2.2测试2.3从库只读​编辑2.4复制的过程原理2.5无磁盘复制2.6复制架构中出现宕机情况,怎么办?3、哨兵(sentinel)3.1什么是哨兵3......
  • MySQL—函数(介绍)—字符串函数(基础)
    一、引言 提到函数,在SQL分类中DQL语句中有一个聚合函数,如COUNT()、SUM()、MAX()等等。这些都是一些常见的聚合函数,而聚合函数只是函数的一种,接下来会详细的学习和介绍一下函数的应用场景和以及mysql当中文件的函数有哪些。二、函数概念:函数是指一段可以直接被另一段程......
  • Redis数据存储和读写
    今天工作群里,有小伙伴问了一个问题,从Redis获取的数据,一会是0,一会是OK。这引起了我们对Redis数据存储和读写的疑问。以下是整理的一些技术研究内容。在Redis中,所有的数据存储都是基于字符串的。无论你插入的是String、int还是DateTime类型的数据,最终都会以字符串的形式存......
  • 深入理解Redis事务、事务异常、乐观锁、管道
    Redis事务与MySQL事务不一样。原子性:MySQL有UndoLog机制,支持强原子性,和回滚。Redis只能保证事务内指令可以不被干扰的在同一批次执行,且没有机制保证全部成功则提交,部分失败则回滚。隔离性:MySQL的隔离性指多个事务可以并发执行,MySQL有MVCC机制。而Redis没有,Redis是事务提交前......