面试如何脱引而出?Redis字符串底层原理你掌握了吗

这篇具有很好参考价值的文章主要介绍了面试如何脱引而出?Redis字符串底层原理你掌握了吗。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

今天我们讲解字符串的底层原理,属于进阶内容,能回答出来可以秒杀80%的面试者。‍

大家都知道Redis有5种基本数据类型,但是你知道每种数据类型对应的底层编码或者数据结构是什么样的吗?

这在面试中是一个有区分度的问题,如果你不会,那么非常有必要继续阅读

这里只列举出不同数据类型的主要编码实现,并非全部。主要的底层编码有这几种:

    •简单动态字符串

    •双向链表

   •整数

    •哈希表

    •压缩列表

    •跳表

    •整数集合

先说明 ,我们这里是以redis 5.0.8版本的代码为准,不同版本之间实现会有区别。

Redis 对象结构

首先补充一下基础知识,Redis对象由redisObject结构体表示。(这里的源码基于5.0.5版本)

typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 所使用的底层编码
    unsigned encoding:4;
    // 对象最后一次被访问的时间 ,和redis 的lru算法实现有关
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    // 引用计数
    int refcount;
    // 重点:指向对象的底层实现数据结构
    void *ptr;
} robj;

Redis中的每个键值对的键和值都是一个redisObject。共有五种类型的对象:字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(SortedSet),源码server.h如下定义:

#define OBJ_STRING 0    /* String object. */字符串
#define OBJ_LIST 1      /* List object. */list
#define OBJ_SET 2       /* Set object. */集合
#define OBJ_ZSET 3      /* Sorted set object. */有序集合
#define OBJ_HASH 4      /* Hash object. */哈希

每种类型的对象至少都有两种或以上的编码方式;可以在不同的使用场景上优化对象的使用场景。用TYPE命令可查看某个键值对的类型。

#define OBJ_ENCODING_RAW 0     /* SDS简单动态字符串 Raw representation */
#define OBJ_ENCODING_INT 1     /* 整数 Encoded as integer */
#define OBJ_ENCODING_HT 2      /* 字典Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* map Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* 压缩列表 Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /*整数集合 Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* 跳跃表Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* 嵌入式SDS Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

本质上,Redis就是基于这些数据结构而构造出一个对象存储系统。redisObject结构体有个ptr指针,指向对象的底层实现数据结构,encoding属性记录对象所使用的编码,即该对象使用什么数据结构作为底层实现。

(关于我对Redis源码的阅读,已经在源码中做了中文注释和笔记,后期会在GitHub开放)

本期我们主要讲解Redis中String的底层编码方式。

String

Redis的String类型在5.0.8版本中主要有三种编码实现,分别是整数、简单动态字符、嵌入式字符串。

SDS

Redis 设计了简单动态字符串(Simple Dynamic String,SDS)的结构,用来表示字符串。相比于 C 语言中的字符串实现,SDS 这种字符串的实现方式,会提升字符串的操作效率,并且可以用来保存二进制数据。

我们可能以为redis在内部存储string都是用sds的数据结构实现的,其实在整个redis的数据存储过程中为了提高性能,内部做了很多优化。整体选择顺序应该是:

    •整数,存储字符串长度小于21且能够转化为整数的字符串。 

    •EmbeddedString,存储字符串长度小于44的字符串。

    •SDS,剩余情况使用sds进行存储。

为什么不用 C 自带 Char*

    1.“\0”的影响,不能存储任意二进制数据

    2.操作函数复杂度,很多复杂度达到On,不能保证有足够的空间,不符合 Redis 对字符串高效操作的需求。

所以在实现字符串时,需要尽量满足以下三个要求:

    1.能支持丰富且高效的字符串操作,比如字符串追加、拷贝、比较、获取长度等

    2.能保存任意的二进制数据,比如图片等

    3.能尽可能地节省内存开销。

事实上,SDS 一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64。这 5 种类型的主要区别就在于,它们数据结构中的字符数组现有长度 len 和分配空间长度 alloc

// EG:sdshdr8
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 字符数组现有长度*/
    uint8_t alloc; /* 字符数组的已分配空间,不包括结构体和\0结束字符*/
    unsigned char flags; /* SDS类型*/
    char buf[]; /*字符数组*/
};

SDS结构设计

首先,SDS 结构里包含了一个字符数组 buf[],用来保存实际数据。同时,SDS 结构里还包含了三个元数据,分别是字符数组现有长度 len、分配给字符数组的空间长度 alloc,以及 SDS 类型 flags。其中,Redis 给 len 和 alloc 这两个元数据定义了多种数据类型,进而可以用来表示不同类型的 SDS。

面试如何脱引而出?Redis字符串底层原理你掌握了吗

另外,如果你在 Redis 源码中查找过 SDS 的定义,那你可能会看到,Redis 使用 typedef 给 char* 类型定义了一个别名,这个别名就是 sds,如下所示:

typedef char *sds;

其实,这是因为 SDS 本质还是字符数组,只是在字符数组基础上增加了额外的元数据。在 Redis 中需要用到字符数组时,就直接使用 sds 这个别名。

嵌入式字符串

尝试将 RAW 编码的字符串编码为 EMBSTR 编码,使用EMBSTR 编码嵌入式字符串,这个对象没办法进行编码,尝试从 SDS 中移除所有空余空间,使用SDS编码

SDS 在保存比较小的字符串时,会使用嵌入式字符串的设计方法,将字符串直接保存在 redisObject 结构体中。然后在 redisObject 结构体中,存在一个指向值的指针 ptr,而一般来说,这个 ptr 指针会指向值的数据结构。这里我们就以创建一个 String 类型的值为例,Redis 会调用 createStringObject 函数,来创建相应的 redisObject,而这个 redisObject 中的 ptr 指针,就会指向 SDS 数据结构,创建RedisObject时,同时也会分配好嵌入式字符串所需的内存空间,如下图所示。

面试如何脱引而出?Redis字符串底层原理你掌握了吗

字符串小于等于44字节时,使用嵌入式字符串,否则创建普通sds字符串

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    //创建嵌入式字符串,字符串长度小于等于44字节
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    //创建普通字符串,字符串长度大于44字节
    else
        return createRawStringObject(ptr,len);
}

首先,createEmbeddedStringObject 函数会分配一块连续的内存空间,这块内存空间的大小等于 redisObject 结构体的大小、SDS 结构头 sdshdr8 的大小和字符串大小的总和,并且再加上 1 字节。注意,这里最后的 1 字节是 SDS 中加在字符串最后的结束字符“\0”。

在 Redis 源码中,createStringObject 函数会根据要创建的字符串的长度,决定具体调用哪个函数来完成创建。那么针对这个 createStringObject 函数来说,它的参数是字符串 ptr 和字符串长度 len。当 len 的长度大于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 这个宏定义时,createStringObject 函数会调用 createRawStringObject 函数,否则就调用 createEmbeddedStringObject 函数。而在我们分析的 Redis 5.0.8 源码版本中,这个 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 默认定义为 44 字节。

embstr和sds的区别在于内存的申请和回收

    •embstr的创建只需分配一次内存,而raw为两次(一次为sds分配对象,另一次为redisObject分配对象,embstr省去了第一次)。相对地,释放内存的次数也由两次变为一次。

    •embstr的redisObject和sds放在一起,更好地利用缓存带来的优势

    •缺点:redis并未提供任何修改embstr的方式,即embstr是只读的形式。对embstr的修改实际上是先转换为raw再进行修改。

整数

只对长度小于或等于 20 字节,并且可以被解释为整数的字符串进行编码,使用整数存储 ,使用整数是最节省空间的做法。

// robj *tryObjectEncoding(robj *o):尝试对字符串对象进行编码,以节约内存
// OBJ_ENCODING_INT 1
...
// 只对长度小于或等于 20 字节,并且可以被解释为整数的字符串进行编码
    if (len <= 20 && string2l(s,len,&value)) {
    ...
      o->encoding = OBJ_ENCODING_INT;
     o->ptr = (void*) value;
     return o;
    ...

加入讨论群是升职加薪第一步!

回复:加群

点赞是一种美德,如对您有帮助,欢迎评论和分享,感谢阅读!

一文读懂MySQL的BinLog写入机制|原创

2023-04-04

面试如何脱引而出?Redis字符串底层原理你掌握了吗

TCC真没这么简单,一文讲透|分布式事务系列(三)

2023-03-29

面试如何脱引而出?Redis字符串底层原理你掌握了吗

从二叉查找树到B*树,一文搞懂搜索树的演进!|金三银四系列

2023-03-25文章来源地址https://www.toymoban.com/news/detail-420288.html

面试如何脱引而出?Redis字符串底层原理你掌握了吗

到了这里,关于面试如何脱引而出?Redis字符串底层原理你掌握了吗的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis教程——Redis string 字符串

    Redis 是一款开源的高性能键值对存储数据库,支持多种数据结构,其中之一是字符串(String)。在 Redis 中,字符串是二进制安全的,这意味着字符串可以包含任意数据,包括图片、音频、视频等。 二进制安全: Redis 字符串是二进制安全的,可以存储任意数据,而不仅限于文

    2024年01月20日
    浏览(40)
  • redis—String字符串

    目录 前言 1.字符串数据类型 2.常见命令 3.典型应用场景 字符串类型是Redis最基础的数据类型,关于字符串需要特别注意: 1)首先Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础.上构建的,例如列表和集合的 元素类型是字符串类型,所以

    2024年02月02日
    浏览(49)
  • 【经典面试】87 字符串解码

    给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string] ,表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 此外,你

    2024年02月08日
    浏览(45)
  • 面试热题(字符串相加)

    给定两个字符串形式的非负整数  num1  和 num2  ,计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库(比如  BigInteger ), 也不能直接将输入的字符串转换为整数形式。       字符串相加这道题其实对于很多人来说是有挑战性的,因为有进

    2024年02月14日
    浏览(41)
  • 面试算法117:相似的字符串

    如果交换字符串X中的两个字符就能得到字符串Y,那么两个字符串X和Y相似。例如,字符串\\\"tars\\\"和\\\"rats\\\"相似(交换下标为0和2的两个字符)、字符串\\\"rats\\\"和\\\"arts\\\"相似(交换下标为0和1的字符),但字符串\\\"star\\\"和\\\"tars\\\"不相似。 输入一个字符串数组,根据字符串的相似性分组,请问

    2024年02月02日
    浏览(40)
  • 字符串相关高频面试题算法

    一、字符串 java:String内置类型,不可更改。(如需更改可考虑:StringBuffer, StringBuilder,char[]等) 二、归类 字符串涉及到的相关题型通常会是以下几个方面: 概念理解:字典序 简单操作:插入删除字符、旋转 规则判断(罗马数字转换 是否是合法的整数、浮点数) 数字运算(

    2024年02月09日
    浏览(73)
  • Redis原理:动态字符串SDS

    (课程总结自b站黑马程序员课程) Redis中保存的Key是字符串,value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串,因为C语言字符串存在很多问题: ①获取字符串长度的需要通过运算 ②非二进制安全:指

    2024年02月09日
    浏览(35)
  • Redis 分批删除字符串键操作

    有时候我们需要清理一些非常大的key(如hash键),或者通配非常多的(如string类型), 如果直接使用keys、del操作会对线上的redis有性能影响,一般建议使用unlink 异步删除操作,释放交给redis自身去处理,但也有一些场景,可能需要快速释放内存,或者通配去删除 ,或者针对

    2024年02月15日
    浏览(43)
  • 面试经典150题:数组/字符串合集

    新专栏,预计两个月写完吧,每天下班回来抽空做几道题。会把做题计划顺序记录下来,如果你有缘,刷到这个开篇序列,那就跟着文章去练题吧。初学者可以慢慢来 88. 合并两个有序数组 27. 移除元素 这是前后指针覆盖做的,也可以双向指针交换做,思路 i从0开始,j从尾

    2024年02月08日
    浏览(42)
  • 【面试经典150 | 动态规划】交错字符串

    本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更…… 专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删: Tag:介绍本题牵涉到的知识点、数据结构; 题目来源:

    2024年04月15日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包