6、Redis系统-数据结构-01-String

Redis 数据结构简介

前言

Redis 是一个高性能的内存数据库,其关键在于其数据结构的设计。Redis 数据结构是指底层实现 Redis 键值对中值的数据类型的方式。它包括了以下几种主要对象:

  1. String(字符串)对象:最基本的数据类型,可以存储任意类型的数据,包括字符串、整数、浮点数等。
  2. List(列表)对象:一个链表,可以按顺序存储多个字符串,支持从两端进行操作。
  3. Hash(哈希)对象:用于存储键值对集合,类似于一个小型的键值对数据库。
  4. Set(集合)对象:一个无序集合,自动去重,支持集合操作如交集、并集等。
  5. Zset(有序集合)对象:类似于 Set,但每个元素都会关联一个权重(score),元素按权重排序。

        随着 Redis 版本的更新,这些数据类型的底层数据结构也有所不同,如双向链表、压缩列表、哈希表、跳表、整数集合、quicklist 和 listpack 等。这些数据结构的选择使得 Redis 在处理数据的增删查改操作时能够高效地运行。

        研究 Redis 的底层数据结构有助于我们深入理解 Redis 的工作原理,优化性能,确保数据的安全性,扩展系统的能力,并更好地排查和解决问题。这对于使用和管理 Redis 数据库以及构建高效可靠的应用程序都是非常有价值的。本文将详细介绍 Redis 的底层数据结构。

一、SDS(Simple Dynamic String,简单动态字符串)

1、SDS的必要性
  1. C 语言字符串的限制

    • 字符数组表示:在 C 语言中,字符串是以字符数组(char*)的形式表示,以\0字符作为结尾标记。
    • 长度计算复杂度高:字符串长度的获取需要遍历整个字符数组,时间复杂度为 O(N)。
    • 无法保存二进制数据:由于\0字符标记字符串结束,字符串中不能包含\0字符,因此无法保存二进制数据。
    • 安全性问题:C 语言标准库中的字符串操作函数如 strcat 存在缓冲区溢出等安全性问题,因为它们不检查缓冲区大小是否足够。
  2. Redis 的需求

    • 高效操作:需要高效获取字符串长度和修改字符串的能力。
    • 二进制安全:需要能够存储和操作二进制数据。
    • 安全性:需要安全的字符串操作,避免缓冲区溢出等问题。

为了克服 C 语言字符串的不足,Redis 采用了 SDS 作为其字符串数据类型的底层数据结构。

2、SDS的数据结构

SDS 的数据结构如下:

struct sdshdr {
    int len;       // 记录了字符串的长度
    int free;      // 分配给字符数组的剩余空间长度
    char buf[];    // 字符数组,用来保存实际数据
};

结构中的每个成员变量分别介绍如下:

  • len:记录了字符串长度。这样在获取字符串长度时,只需要返回这个成员变量值即可,时间复杂度为 O(1)。
  • alloc:分配给字符数组的剩余空间长度。在修改字符串时,可以通过 alloc - len 计算出剩余的空间大小,用于判断是否需要扩展空间。
  • buf[]:字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。

总的来说,Redis 的 SDS 结构在原本字符数组之上,增加了三个元数据:lenfreeflags,用来解决 C 语言字符串的缺陷。

3、SDS的优势
  1. O(1) 复杂度获取字符串长度

    • C 语言的 strlen 函数需要遍历字符串,时间复杂度为 O(N)。
    • SDS 通过 len 成员变量直接返回字符串长度,时间复杂度为 O(1)。
  2. 二进制安全

    • SDS 不需要 \0 字符标识字符串结尾,而是通过 len 成员变量记录长度,可以存储包含 \0 的数据。
    • 为了兼容部分 C 语言标准库函数,SDS 字符串结尾仍会加上 \0 字符。
    • SDS 的 API 处理数据时以二进制方式处理,程序不会对数据做任何限制,保证数据的原样读取和写入。
  3. 避免缓冲区溢出

    • C 语言字符串操作函数如 strcat 把缓冲区大小是否满足操作需求的检查交给开发者,存在缓冲区溢出的风险。
    • SDS 通过 alloc 和 len 成员变量,在进行字符串修改时由程序内部判断缓冲区大小是否足够。
    • 当缓冲区大小不够用时,Redis 会自动扩展 SDS 的空间大小,满足修改所需的空间。

    SDS 的扩容规则如下:

    • 如果所需的 SDS 长度小于 1 MB,扩容按翻倍进行,即 2 倍的 newlen
    • 如果所需的 SDS 长度超过 1 MB,扩容长度为 newlen + 1MB
  4. 节省空间

    • SDS 设计了 5 种类型的结构体(sdshdr5sdshdr8sdshdr16sdshdr32sdshdr64),根据 len 和 alloc 成员变量的数据类型不同来适应不同大小字符串的存储需求。
    • 不同的数据类型限制了字符数组长度和分配空间大小的上限,从而有效地节省内存空间。
4、SDS 数据结构的实现

以下是 SDS 的实现示例,包括创建、释放、拼接和复制操作:

  1. 创建 SDS

    sds sdsnew(const char *init) {
        size_t initlen = (init == NULL) ? 0 : strlen(init);
        sds s = sdsnewlen(init, initlen);
        return s;
    }
    
  2. 释放 SDS

    void sdsfree(sds s) {
        if (s == NULL) return;
        zfree((char*)s - sizeof(struct sdshdr));
    }
    
  3. 拼接字符串

    sds sdscat(sds s, const char *t) {
        return sdscatlen(s, t, strlen(t));
    }
    
  4. 复制字符串

    sds sdscpy(sds s, const char *t) {
        return sdscpylen(s, t, strlen(t));
    }
    
  5. 获取 SDS 长度

    size_t sdslen(const sds s) {
        struct sdshdr *sh = (void*) (s - (sizeof(struct sdshdr)));
        return sh->len;
    }
    
  6. 清空 SDS

    void sdsclear(sds s) {
        struct sdshdr *sh = (void*) (s - (sizeof(struct sdshdr)));
        sh->free += sh->len;
        sh->len = 0;
        s[0] = '\0';
    }
    
5、整数类型存储优化

在 Redis 中,如果字符串内容可以表示为数字类型,通常可以优化存储为 long 类型或整数集合(intset)。这是因为整数类型的存储和操作通常比字符串更高效。

  1. 使用整数类型存储数字字符串

    • Redis 的字符串对象可以存储整数类型的值。如果字符串可以被解析为整数,Redis 会将其转换为整数类型进行存储。例如,执行 SET key "12345" 时,Redis 会将其存储为整数编码(INT),而不是字符串编码。
  2. 整数集合(intset)

    • 整数集合是一种紧凑的有序集合,适用于存储小范围的整数集合。它的内部结构如下:
    typedef struct intset {
        uint32_t encoding; // 编码类型(int16, int32, int64)
        uint32_t length;   // 集合中元素的个数
        int8_t contents[]; // 元素数组
    } intset;
    
结论

通过上述解析,我们可以更好地理解 SDS 的设计思想和实现原理,从而在实际开发中更好地利用 SDS 提供的优势。在 Redis 中,字符串可以表示为数字类型时,会自动转换为整数类型进行存储,以提高存储和操作效率。此外,Redis 提供了整数集合(intset)数据结构,用于高效存储一组整数。了解这些优化策略

,可以帮助我们在实际应用中更好地利用 Redis 的性能和功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java核心-泛型

目录 概述什么是泛型分类泛型类泛型接口泛型方法 泛型通配符分类 泛型类型擦除分类无限制类型擦除有限制类型擦除 问题需求第一种第二种 概述 了解泛型有利于学习 jdk 、中间件的源码,提升代码抽象能力,封装通用性更强的组件。 什么是泛型 在定义类、接…

存储过程编程-创建(CREATE PROCEDURE)、执行(EXEC)、删除(DROP PROCEDURE)

一、定义 1、存储过程是在SQL服务器上存储的已经编译过的SQL语句组。 2、存储过程分为三类:系统提供的存储过程、用户定义的存储过程和扩展存储过程 (1)系统提供的存储过程:在安装SQL Server时,系统创建了很多系统存…

Kafka(一)基础介绍

一,Kafka集群 一个典型的 Kafka 体系架构包括若Producer、Broker、Consumer,以及一个ZooKeeper集群,如图所示。 ZooKeeper:Kafka负责集群元数据的管理、控制器的选举等操作的; Producer:将消息发送到Broker…

MySQL事务隔离

MySQL事务隔离 前言锁共享锁(Shared Lock)排他锁(Exclusive Lock)行级锁(Row-Level Lock)表级锁(Table-Level Lock)快照读和当前读查看锁 事务事务的四个特性事务的并发问题事务的隔…

Chrome 127内置AI大模型攻略

Chrome 127 集成Gemini:本地AI功能 Google将Gemini大模型整合进Chrome浏览器,带来全新免费的本地AI体验: 完全免费、无限制使用支持离线运行,摆脱网络依赖功能涵盖图像识别、自然语言处理、智能推荐等中国大陆需要借助魔法,懂都懂。 安装部署步骤: 1. Chrome V127 dev …

golang验证Etherscan上的智能合约

文章目录 golang验证Etherscan上的智能合约为什么要验证智能合约如何使用golang去验证合约获取EtherscanAPI密钥Verify Source Code接口Check Source Code Verification Status接口演示示例及注意事项网络问题无法调用Etherscan接口(最重要的步骤) golan…

YoloV9改进策略:Block改进|轻量实时的重参数结构|最新改进|即插即用(全网首发)

摘要 本文使用重参数的Block替换YoloV9中的RepNBottleneck,GFLOPs从239降到了227;同时,map50从0.989涨到了0.99(重参数后的结果)。 改进方法简单,只做简单的替换就行,即插即用,非常…

保健品商城小程序模板源码

保健品商城小程序模板源码 简洁通用的保健品,健康生活,零售商品,电子商务微信小程序前端模板下载。包含:主页、购物车、客服、个人中心、我的订单、商品详情、我的钱包、设置等等。 保健品商城小程序模板源码

程序员如何做好需求判断?

1. 导语 本文作为2024上半年核心思考之二。 通过他人经验传导、个人实践、广泛阅读书籍(方法论类、企业经营类、传记类、财务类,具体书单附文末),学会基于更高阶的经营者视角来做好业务需求判断。本文思路如下: 首先,抛一个灵魂问…

【server】springboot 整合 redis

1、redis 使用模式 1.1 单机模式 1.1.1 编译安装方式 1.1.1.1 下载 Redis的安装非常简单,到Redis的官网(Downloads - Redis),下载对应的版本,简单几个命令安装即可。 1.1.1.2 编译安装 tar xzf redis-stable.tar.…

IDEA 开发工具

IDEA 开发工具 IDEA软件激活新建项目新建project 运行调试 IDEA软件激活 访问激活码网进入带*的域名下载并解压左上角的zip包先执行sh uninstall.sh,再执行sh install.sh在带*的网页中复制并使用激活码code 新建项目 新建project file》New〉Project》New Proje…

【测试】系统压力测试报告模板(Word原件)

系统压力测试,简而言之,是在模拟高负载、高并发的环境下,对系统进行全面测试的过程。它旨在评估系统在面对极端使用条件时的性能表现,包括处理能力、响应时间、资源消耗及稳定性等关键指标。通过压力测试,开发团队能够…

MySQL之备份与恢复和MySQL用户工具(一)

备份与恢复 备份脚本化 为备份写一些脚本是标准做法。展示一个示例程序,其中必定有很多辅助内容,这只会增加篇幅,在这里我们更愿意列举一些典型的备份脚本功能,展示一些Perl脚本的代码片段。你可以把这些当作可重用的代码块&…

Python酷库之旅-第三方库Pandas(009)

目录 一、用法精讲 19、pandas.read_xml函数 19-1、语法 19-2、参数 19-3、功能 19-4、返回值 19-5、说明 19-6、用法 19-6-1、数据准备 19-6-2、代码示例 19-6-3、结果输出 20、pandas.DataFrame.to_xml函数 20-1、语法 20-2、参数 20-3、功能 20-4、返回值 …

【国产开源可视化引擎Meta2d.js】网格

画布背景网格 在线体验: 乐吾乐2D可视化 示例: // 设置默认缺省网格属性 meta2d.store.options.grid true; // 开启 meta2d.store.options.gridColor eeeeee; // 网格线条颜色 meta2d.store.options.gridSize 10; // 格子大小// 设置单个图纸的网格…

Golang | Leetcode Golang题解之第222题完全二叉树的节点个数

题目&#xff1a; 题解&#xff1a; func countNodes(root *TreeNode) int {if root nil {return 0}level : 0for node : root; node.Left ! nil; node node.Left {level}return sort.Search(1<<(level1), func(k int) bool {if k < 1<<level {return false}…

【ETABS】【RHINO】案例:Swallow to ETABS

文章目录 01. Swallow Overview总览1 LOAD&#xff1a;Defination of LoadCase、Response Combo2 SectionArea Section and Area Load&#xff08;面截面定义与指定&#xff0c;面荷载指定&#xff09;Frame Section with rebarattr and linear load&#xff08;带钢筋属性框架…

flutter:监听路由的变化

问题 当从路由B页面返回路由A页面后&#xff0c;A页面需要进行数据刷新。因此需要监听路由变化 解决 使用RouteObserver进行录音监听 创建全局变量&#xff0c;不在任何类中 final RouteObserver<PageRoute> routeObserver RouteObserver<PageRoute>();在mai…

Hi3861 OpenHarmony嵌入式应用入门--UDP Server

本篇使用的是lwip编写udp服务端。需要提前准备好一个PARAM_HOTSPOT_SSID宏定义的热点&#xff0c;并且密码为PARAM_HOTSPOT_PSK。 修改网络参数 在Hi3861开发板上运行上述四个测试程序之前&#xff0c;需要根据你的无线路由、Linux系统IP修改 net_params.h文件的相关代码&…

基于轨迹信息的图像近距离可行驶区域方案验证

一 图像可行驶区域方案 1.1 标定场景 1.2 标定步骤 设计一定间距标定场&#xff0c;在标定场固定位置设置摄像头标定标识点。主车开到标定场固定位置录制主车在该位置各个摄像头数据&#xff0c;通过摄像头捕获图像获取图像上关键点坐标pts-2d基于标定场设计&#xff0c;计算…