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

news2024/11/27 0:28:50

今天我们讲解字符串的底层原理,属于进阶内容,能回答出来可以秒杀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。

7e52cc2e628e529efcd34ed47c9b78bf.png

另外,如果你在 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时,同时也会分配好嵌入式字符串所需的内存空间,如下图所示。

49f074352adc5220191b265527063bc3.png

字符串小于等于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;
    ...

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

回复:加群

b564d34f68fe8e9acf7f203bd4cc0fa4.jpeg

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

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

2023-04-04

c7920849aa2ebb73a0b62bfd4de60fd3.jpeg

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

2023-03-29

a00d535cffeeffe45978bf99671cd35c.jpeg

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

2023-03-25

c3e9a0e4fc30dbb21d6ee1b5fdacc911.jpeg

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

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

相关文章

北京E4432B信号发生器

E4432B 安捷伦|Agilent E4432B ESG-D系列 3G高频数字信号源250KHz-3GHz 品  牌&#xff1a; Agilent简单介绍频率范围&#xff1a;E4430B 250K-1GHzE4431B 250K-2GHzE4432B 250K-3GHzE4433B 250K-4GHz 18320918653供单信道和多信道CDMA用的测量卡用于I和Q的20 MHz射频带宽…

搞懂 API :API 测试中常见的问题及处理办法

API测试是Web服务质量保证的重要环节之一。它可以有效地检查API是否符合预期&#xff0c;但在操作中也经常遇到各种问题。本文将介绍API测试中常见的问题以及如何解决它们。 接口错误码不清楚或无法处理 接口开发人员往往会为其API定义错误码来表示特定的错误类型&#xff0c;…

在新电脑上重启自己的django+vue项目遇到的数据库和Nodejs问题

数据库问题 今天在新电脑上重启自己备份的项目文件,迁移数据库的时候一直出现这个问题,运行代码也是这个问题。很奇怪,明明是不出错的代码做的备份怎么会出问题? 找了一下午,终于晚上把问题解决了。 问题 1、首先是这个问题 2、再往上追溯,发现是这里的问题 3、在原…

学习实践-Alpaca-Lora (羊驼-Lora)(部署+运行)

Alpaca-Lora模型GitHub代码地址 1、Alpaca-Lora内容简单介绍 三月中旬&#xff0c;斯坦福发布的 Alpaca &#xff08;指令跟随语言模型&#xff09;火了。其被认为是 ChatGPT 轻量级的开源版本&#xff0c;其训练数据集来源于text-davinci-003&#xff0c;并由 Meta 的 LLaMA …

11-FastDFS

一 为什么要使用分布式文件系统 单机时代 初创时期由于时间紧迫&#xff0c;在各种资源有限的情况下&#xff0c;通常就直接在项目目录下建立静态文件夹&#xff0c;用于用户存放项目中的文件资源。如果按不同类型再细分&#xff0c;可以在项目目录下再建立不同的子目录来区分…

Verilog | 轮询仲裁

仲裁 当多个源和用户需要共享同一资源时&#xff0c;需要某种仲裁形式&#xff0c;使得所有用户基于一定的规则或算法得到获取或访问共享资源的机会。 仲裁方案 严格优先级轮询 根据优先级的差异&#xff0c;用户访问共享资源的机会也不同。低优先级的用户可能时钟无法得到资…

数据智能服务商奇点云完成近亿元C2轮融资

奇点云集团宣布已于2022年底完成近亿元C2轮融资&#xff0c;余杭国投领投&#xff0c;中银渤海基金跟投。 截至目前&#xff0c;奇点云共获近3亿元C轮融资。C轮领投方包括泰康人寿&#xff08;旗下泰康资产执行&#xff09;、余杭国投&#xff0c;跟投方包括字节跳动、德同资本…

九龙证券|巴菲特又出手了!嗅到了什么?日本央行新行长发声

巴菲特或再发日元债&#xff0c;嗅到了什么&#xff1f; 有商场消息称&#xff0c;知情人士泄漏&#xff0c;沃伦巴菲特旗下伯克希尔哈撒韦已开端就出售日元债券向投资者征求意见。这似乎印证了稍早之前的信息&#xff0c;此前&#xff0c;据彭博报导&#xff0c;伯克希尔哈撒韦…

实验手册 - 第5周Pair RDD与分区

目录标题实验1实验2实验3实验4实验5import findspark findspark.init() from pyspark import SparkContextsc SparkContext()实验1 实验1&#xff1a;已知内存数据源 list01 [1, 2, 3, 4, 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44, 51, 52, 53, 54] …

redis数据结构底层原理及相关运用

Redis的数据结构 Redis的数据结构&#xff0c;可以在两个不同的层面来讨论它。 第一个层面&#xff0c;是从使用者的角度。比如&#xff1a;string、list、hash、set、zset(sorted set)五种数据类型 这一层面也是Redis暴露给外部的调用接口&#xff0c;也就是我们平时使用re…

多态-虚函数表

VS的对象内存分析: /d reportSingleClassLayout类名 使用方法:项目 -- 属性 -- C/C -- 命令行--其他选型(D) 添加命令.如图所示: Father类: class Father { public:virtual void Func1() { cout << "Father::Func1" << endl; }virtual void Func2() { c…

【Redis】亿级数据的收集+清洗+统计+展现

文章目录聚合统计(set->共同好友)排序统计(zset->评论排序)二值统计(bitmap->签到打卡)基数统计(hyperloglog->亿级UV统计方案)地理坐标(GEO)布隆过滤器面试题定义产生背景作用底层原理聚合统计(set->共同好友) 统计多个集合元素的聚合结果&#xff0c;就是前面…

DF竞赛平台助力首届“深水云脑杯”全国智慧水务数据创新大赛圆满落幕

首届“深水云脑杯”全国智慧水务数据创新大赛决赛现场 首届“深水云脑杯”全国智慧水务数据创新大赛已圆满落幕&#xff0c;DataFountain大数据竞赛平台&#xff08;简称DF平台&#xff09;作为官方竞赛平台为本次大赛提供办赛支持。该赛事以数字化创新模式为抓手&#xff0c;…

从零学习SDK(6)调试和测试SDK的库

在前面的文章中&#xff0c;我们介绍了什么是SDK&#xff0c;以及如何选择和接入合适的SDK。在本文中&#xff0c;我们将重点讲解如何调试和测试SDK的库&#xff0c;以确保我们的应用能够正常运行&#xff0c;没有错误或异常。 SDK的库是什么呢&#xff1f;简单来说&#xff0…

DAF Trucks EDI项目案例

DAF Trucks是一家荷兰卡车制造商&#xff0c;通过EDI系统与其供应商和客户之间进行电子交换。DAF Trucks EDI系统包括订单处理、发货通知、发票和付款等功能&#xff0c;能够快速、准确地交换业务文档&#xff0c;提高供应链管理水平。DAF计划将其EDI系统扩展到更多的供应商和客…

JavaScript学习笔记(二)

文章目录第4章&#xff1a;变量、作用域与内存1. 原始值与引用值2. 执行上下文与作用域3. 垃圾回收第5章&#xff1a;基本引用类型1. Date&#xff1a;参考了Java早期版本中的java.util.Date2. RegExp3. 原始值包装类型第6章&#xff1a;集合引用类型1. Object2. Array&#xf…

三电技术之电池管理技术

三电技术之电池管理技术 1 功能概述 电池管理系统 (Battery Management System), 即管理电池的充放电&#xff0c;使电池处于一个最佳的状态。 电池是由多个电芯组成的&#xff0c;每个电芯充放电都是一个电化学反应的过程。无论电芯的制造多精密&#xff0c;随着使用时间、…

OpenText 企业内容管理平台介绍

OpenText 企业内容管理平台介绍 将 ECM 扩展到领先的业务应用程序中&#xff0c;为内容添加上下文&#xff0c;从而提高效率和决策能力 突出优点&#xff1a; 1、企业拥有更多数据、更多来源、更多用途并按需提供 2、员工需要一种新的交互、共享和消费内容的方式 3、更多内容需…

系统分析师冲刺班练习题

系统配置与性能评价---性能指标 吞吐量是指网络、设备、端口、虚拟电路或其他设备&#xff0c;单位时间内成功地传送数据的数量&#xff08;以比特、字节、分组等测量&#xff09; 系统配置与性能评价---性能评价方法 指令执行速度法&#xff1a;在计算机发展的初期&#xff…

【C++】2.C++的输入与输出

文章目录前言一、C的输入\出头文件二、C的输入&输出关键字2.1 输出cout2.2 输入cin三、c输入输出与c语言的输入输出前言 c语言中我们使用scanf,printf等来进行输入、输出操作&#xff0c;在C中我们是否有其他方式呢&#xff1f;答案是有的&#xff0c;下面我们来介绍c的输…