首页 > 数据库 >面试问 Redis 的字符串原理是什么?答不出被 Pass 了!

面试问 Redis 的字符串原理是什么?答不出被 Pass 了!

时间:2024-05-25 09:25:27浏览次数:27  
标签:__ SDS 答不出 Redis len char Pass 字符串

引言:在 Redis 中,并没有使用 C 标准库提供提供的字符串,而是实现了一种动态字符串,即 SDS (Simple Dynamic String),然后通过这种数据结构来表示字符串,面试中除了基本数据类型让你去讲解,此外还会讲1-2种数据结构的底层原理和优势。

题目

redis 的字符串为什么要升级 SDS,而不用 C 语言字符串?

推荐解析

C 语言字符串的缺陷

首先最直接的,为什么不适用一个东西,其最根本的原因就是他有不好的地方, C 语言的字符串其实本质上就是 char* 的字符数组,其本身是存在一定缺陷的,首先我们来盘点一下其缺陷。

1)C 语言字符数组的结尾位置就用 “\0” 表示,意思是指字符串的结束

2)C 语言字符数组获取长度只能通过遍历获得,时间复杂度是 O(n)

3)字符串操作函数不高效且不安全,比如缓冲区溢出,其可能导致程序异常终止

针对以上问题,C 语言在其基础上进行了一定的改进,接下里我们来注意进行分析。

SDS 结构

图片

如下图所示,SDS 数据结构分为四个部分的内容,如下图所示

图片

1)len (长度):记录了 SDS 字符串数组的长度,当需要获取字符串长度的时候,只需要返回这个成员变量的值就可以了,时间复杂度是O(1)

2)alloc(分配空间长度):这个字段的主要作用是指分配给字符数组的存储的空间大小,当需要计算剩余空间大小的时候,只需要 alloc - len 就可以直接进行计算,然后判断空间大小是否符合修改需求,如果不满足需求的话,就执行相应的修改操作,这样的话就可以很好地解决我们上面所说的缓冲区溢出问题。

3)flags(表示 SDS 的类型):一共设计了五种类型的 SDS,分别是 sdshdr 5、sdshdr 8、sdshdr 16、sdshdr 32、sdshdr 64(这个的记忆页很简单,就是 32 开始,128,即 2 的多少次方去记忆就可以了),这个类型的主要作用就是灵活保存不同大小的字符串,从而有效节省内存空间。

4)buf(存储数据的字符数组):主要起到保存数据的作用,如字符串、二进制数据(二进制安全就是一个重要原因)等

然后在分析完 SDS 的结构之后,接下来我们来分析原因,我们可以发现,SDS 相对于 C 语言原生的字符串,其多了几个字段,即 len、alloc、flags,这几个字段帮助 SDS 解决了 C 语言字符串的问题:

1)长度计算

首先是 len,他记录了 SDS 的字符串长度,因此当你需要获取字符串长度的时候,你只需要返回 len 的值就可以了,不需要再去遍历字符串,这样操作的时间复杂度就降低了许多,直接从 O(n) 变成了 O (1)

2)二进制安全

第二个点在于存储的字符数组,SDS 中进行了改进,SDS 中不在需要 \0 来判断字符串是否结束,这就是我们上面所说的 Redis 字符串中的 buf 数组可以存储任何的二进制数据,因此存储二进制数据的时候便不会发生字符截断的问题,避免了由于特殊字符引发的异常,不过需要注意一个点,Redis 为了兼容 C 标准库的一些操作, Redis 仍然为末尾的 \0 预留了内存空间

3)修改操作高效

其次就是 alloc 字段了,alloc 字段用来记录字符串预分配的内存大小,当发生字符串修改的时候,通过 allloc - len 判断当前空间是否足够,如果不足够的话就进行扩容。

然后这里放一段 sds 扩容的代码,用于帮助理解

hisds hi_sdsMakeRoomFor(hisds s, size_t addlen)
{
    ... ...
    // avail 值就是 allloc-len 的值
    // 如果值的大小即剩余空间大于增加的字符串长度,则表示空间足够,不需要进行扩容,直接返回就可以
    if (avail >= addlen)
        return s;
        
    //获取当前 s 的长度大小
    len = hi_sdslen(s);
    sh = (char *)s - hi_sdsHdrSize(oldtype);
    
    //求扩容以后 s 需要的最小长度
    newlen = (len + addlen);
    
    //根据获取到的新长度,为 s 分配新空间所需要的内存空间大小
    if (newlen < HI_SDS_MAX_PREALLOC)
        //新长度 < HI_SDS_MAX_PREALLOC,这里 HI_SDS_MAX_PREALLOC 的值 为 1 MB
        //表示分配所需空间 2 倍的空间
        newlen *= 2;
    else
        //不满足条件的话,分配长度为目前长度 + 1MB 的空间
        newlen += HI_SDS_MAX_PREALLOC;
       ...
}

我们可以发现,当 sds 扩容的时候,其是根据 sds 的长度进行判断的,其判断的值就是所需要的 sds 的长度是否超过 1 MB

  • 如果需要的 sds 长度小于 1MB 的话,其扩容规则就是翻倍扩容,即 2 倍的新长度

  • 如果需要的 sds 长度大于等于 1 MB 的话,其扩容规则就是 newlen + 1 MB

在扩容之前,SDS 会有限检查使用空间够不够使用,如果不够使用的话,则会进行扩容处理,即分配额外的未使用空间,这样可以有效地减少内存分配的次数,并且解决了缓冲区溢出的问题。

4)按需使用,节省内存

最后一个要介绍的点就是 flag 字段,和那个字段主要分为 5 种类型,即 sdshdr5、sdshdr8、sdshdr16、sdshdr32 以及 sdshdr64 五种字符串,他们分别对应存储长度小于等于 2 的 5/8/16/32/64 次方字节的字符串。

通过使用不同存储类型的结构题,灵活保存不同大小的字符串,从而节省内存空间,此外,Redis SDS 底层还使用了消除内存对齐的方式进行内存的优化,从而保证所有的成员尽可能在内存中相邻,从而保证按照实际大小分配内存,节约内存的使用。

源码版本迭代

Redis 3.2 版本以前的源码

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

Redis 3.2 版本以后的源码

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
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[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

其他补充

鱼聪明 AI 的回答:

鱼聪明 AI 地址:https://www.yucongming.com/

图片

Redis 字符串:

优点:

  1. 分布式存储: Redis 是一种分布式缓存系统,可以在多台服务器上进行数据存储和访问,提高了系统的可扩展性和容错性。

  2. 支持持久化: Redis 支持将数据持久化到磁盘,保证数据不会因为服务器重启或断电而丢失。

  3. 数据结构丰富: Redis 的字符串不仅仅支持简单的键值对,还支持列表、哈希、集合等多种数据结构,提供了更多的灵活性和功能。

  4. 高性能: Redis 是基于内存的数据库,读写速度非常快,适用于高并发场景。

缺点:

  1. 内存消耗较高: Redis 数据存储在内存中,如果数据量较大,可能会消耗大量的内存资源。

  2. 单个值大小限制: Redis 单个字符串值的大小受到内存限制,如果需要存储大对象,可能会有限制。

  3. 学习成本: Redis 是一种新的数据存储技术,需要学习其特有的命令和使用方式。

普通 C 语言字符串:

优点:

  1. 简单易用: C 语言中的字符串是一种基本数据类型,使用起来非常简单和灵活。

  2. 低资源消耗: 普通 C 语言字符串通常存储在栈上或堆上,内存消耗较低。

  3. 无需学习成本: 对于熟悉 C 语言的开发者来说,使用普通的 C 语言字符串无需学习新的技术和命令。

缺点:

  1. 不支持分布式: 普通 C 语言字符串只能存储在单个程序的内存空间中,无法实现分布式存储和访问。

  2. 不支持持久化: C 语言字符串通常存储在内存中,程序结束或崩溃时数据会丢失。

  3. 功能受限: 普通 C 语言字符串功能相对简单,不支持丰富的数据结构和高级功能。

标签:__,SDS,答不出,Redis,len,char,Pass,字符串
From: https://blog.csdn.net/2401_83491857/article/details/139162229

相关文章

  • redis自学(44)多级缓存
              就是把注释全都删了  这里指的是OpenResty的Nginx配置文件   请求参数处理    先修改Nginx配置文件 修改lua文件,然后重启nginx   查询Tomcat   写lua文件做工具类      ......
  • 整理好了!2024年最常见 20 道 Redis面试题(四)
    上一篇地址:整理好了!2024年最常见20道Redis面试题(三)-CSDN博客七、Redis单线程模型是如何工作的?Redis是一个基于单线程模型的高性能键值存储数据库。尽管Redis操作大多数是单线程执行的,但它依然能够提供极高的性能,这主要得益于以下几个方面:单线程模型:Redis的所有操......
  • Redis安装学习记录
    一、Redis介绍Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过RedisSentinel提供高可用......
  • nodejs + express + mysql + redis 基础功能实现
    nodejs+express+mysql+redis基础功能实现yeyue  9人赞同了该文章本文大体介绍了nodejs项目的创建、express框架的使用、mysql数据库的连接、以及redis的数据交互等方法,并举例了些简单的例子进行说明,代码都是亲自重头跑了一遍的,拿来可用。 一、......
  • Ubuntu 22.04 使用self-service-password搭建自主修改密码平台
    Ubuntu系统安装准备正常操作是安装成功系统,安装的时候设置好静态密码。参考官方文档:https://self-service-password.readthedocs.io/en/stable/installation.html根据文档提示安装会报错,网上查了些资料需要按照如下步骤安装依赖。正式安装vi/etc/apt/sources.list.d/ltb-p......
  • windows下使用redis解决.net6.0下人工调用接口时分配位置的并发问题
    使用了nuget包包括了:CSRedisCore,StackExchange.Redis,MyStack.DistributedLocking,Microsoft.Extensions.Configuration安装Redis并注册为windows服务 直接参考这位兄弟的成果:https://www.cnblogs.com/qingheshiguang/p/17952623注册服务:配置文件appsetting.json中加上Red......
  • Redis篇
    ----------------------------------------------------------------------------------------------------------------默认端口:6379----------------------------------------------------------------------------------------------------------------前端启动:/usr/local/redi......
  • 远程连接redis
    连接服务器上的redis所需要的操作1、copy一份redis.conf文件,并修改以下内容#开启后台登录daemonizeyes#允许所有连接访问,包括外部连接bind0.0.0.0#不使用密码也可以登陆protected-modeno2、修改完成后使用这个配置文件启动redis#redis服务器启动......
  • redis学习
    Redis简介数据库Redis数据存储在内存中(内存数据库),存储数据为Key-Value形式,单线程(指网络IO以及数据读写只由一个线程完成),其他功能例如持久化,异步删除,集群数据同步等是由额外的线程完成的,采用epoll异步IO多路复用,Redis所有操作均为原子操作,能够确保数据的一致性和完整性,广泛用于缓......
  • 【ubuntu】22.04安装Redis Insight及AnotherRedisDesktopManager
    一、RedisInsight1、官网下载https://redis.io/insight/#insight-form  2、安装sudodpkg-iRedisInsight-linux-amd64.deb 3、运行  二、AnotherRedisDesktopManager1、官网下载https://github.com/qishibo/AnotherRedisDesktopManager/releases/tag/v1.......