Redis中字符串表示是如何设计与实现的?

news2024/11/15 11:35:17

文章目录

  • Redis中字符串表示是如何设计与实现的(SDS)?
    • 引言
    • 简单动态字符串
    • 底层数据结构
    • 为什么不用char *
    • 举个🌰
    • 如何优化append操作?
    • 总结

Redis中字符串表示是如何设计与实现的(SDS)?

引言

在redis中,其键值对中,值可以是数字、字符串或者集合等;但其键key却始终是 字符串 类型。
那么,redis中的字符串底层到底是如何设计的呢?为什么redis要这样设计呢?
本篇文章来详细介绍一下,带你一起了解和学习redis中字符串的设计与实现,不惧面试。

简单动态字符串

Sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示,它被用在几乎所有的 Redis 模块中。
SDS是Redis中用于表示字符串值的数据结构,它是一种动态字符串实现。与C语言中的字符串相比,SDS具有更多的特性和功能。SDS的设计目标是在保持高性能的同时,提供较为灵活的字符串操作接口。

底层数据结构

SDS的内部结构如下所示:

struct sdshdr {
    int len;        // 字符串当前长度
    int free;       // 字符串剩余空间
    char buf[];     // 字符串数据
};

SDS的实际存储空间大小为len + free + 1,其中len表示字符串的当前长度,free表示字符串的剩余空间,buf是一个字节数组,用于存储字符串数据。SDS的结构允许字符串长度的动态增长和缩减,且不需要进行内存的重新分配。

为什么不用char *

将 sds 代替 C 默认的 char* 类型

因为 char* 类型的功能单一,抽象层次低,并且不能高效地支持一些 Redis 常用的操作(比 如追加操作和长度计算操作),所以在 Redis 程序内部,绝大部分情况下都会使用 sds 而不是 char* 来表示字符串。
同时:在 Redis 中,客户端传入服务器的协议内容、aof 缓 存、返回给客户端的回复,等等,这些重要的内容都是由都是由 sds 类型来保存的。

在 C 语言中,字符串可以用一个 \0 结尾的 char 数组来表示。 比如说,hello world 在 C 语言中就可以表示为 “hello world\0” 。这种简单的字符串表示在大多数情况下都能满足要求,但是,它并不能高效地支持长度计算和 追加(append)这两种操作:

  • 每次计算字符串长度(strlen(s))的复杂度为 θ(N) 。
  • 对字符串进行 N 次追加,必定需要对字符串进行 N 次内存重分配(realloc)。

在 Redis 内部,字符串的追加和长度计算并不少见,而 APPEND 和 STRLEN 更是这两种操 作在 Redis 命令中的直接映射,这两个简单的操作不应该成为性能的瓶颈。
另外,Redis 除了处理 C 字符串之外,还需要处理单纯的字节数组,以及服务器协议等内容, 所以为了方便起见,Redis 的字符串表示还应该是二进制安全的:程序不应对字符串里面保存 的数据做任何假设,数据可以是以 \0 结尾的 C 字符串,也可以是单纯的字节数组,或者其他 格式的数据。
考虑到这两个原因,Redis 使用 sds 类型替换了 C 语言的默认字符串表示:sds 既可以高效地 实现追加和长度计算,并且它还是二进制安全的。

举个🌰

  • 作为例子,以下是新创建的,同样保存 hello world 字符串的 sdshdr 结构:
struct sdshdr { 
	len = 11; 
	free = 0;
	buf = "hello world\0"; // buf 的实际长度为 len + 1 
};
  • 通过 len 属性,sdshdr 可以实现复杂度为 θ(1) 的长度计算操作。
  • 另一方面,通过对 buf 分配一些额外的空间,并使用 free 记录未使用空间的大小,sdshdr 可
    以让执行追加操作所需的内存重分配次数大大减少。 当然,sds 也对操作的正确实现提出了要求——所有处理 sdshdr 的函数,都必须正确地更新len 和 free 属性,否则就会造成 bug

如何优化append操作?

好的,以下是sds的源码分析,主要涉及预分配机制和连续增长策略的实现细节:

一、预分配机制

在sds中,预分配机制是通过sdsMakeRoomFor函数实现的。当执行append操作时,该函数会根据需要增加的空间大小计算出预分配的大小,并重新分配内存。预分配的大小通常会比实际需要的大小要大一些,以确保足够的空间进行后续的追加操作。

在源码中,sdsMakeRoomFor函数的实现如下:

sds sdsMakeRoomFor(sds s, size_t addlen) {  
    struct sdshdr *sh;  
    size_t free = sdsavail(s);  
    size_t len = sdslen(s);  
    char *newptr;  
    size_t newlen = len + addlen;  
  
    if (free < addlen) {  
        newptr = zmalloc(newlen + free); // 预分配额外的空间  
        memcpy(newptr, s, len); // 将原有内容复制到新空间中  
        zfree(s); // 释放原有内存空间  
        sh = (struct sdshdr *) newptr;  
        sh->free = free; // 更新未使用空间大小  
    } else {  
        sh = sdshdr(s);  
    }  
    sh->len = newlen; // 更新字符串长度  
    return s; // 返回新的sds字符串指针  
}

在函数中,首先计算出当前sds字符串的未使用空间free和字符串长度len。然后,根据需要增加的空间大小addlen计算出新的字符串长度newlen。如果当前未使用空间不足以容纳增加的空间,则分配额外的内存空间,并将原有内容复制到新空间中。最后,更新sds结构体中的相关字段,并返回新的sds字符串指针。

二、连续增长策略

在sds中,连续增长策略是通过sdsIncrLen函数实现的。该函数会在原有字符串的基础上追加新的内容,并更新sds结构体中的相关字段。如果原有字符串的缓冲区不足以容纳新的内容,该函数会重新分配更大的内存空间,并将原有内容复制到新空间中。这样可以确保追加操作不会频繁地触发内存重分配,从而提高性能。

在源码中,sdsIncrLen函数的实现如下:

sds sdsIncrLen(sds s, int len) {  
    struct sdshdr *sh = sdshdr(s);  
    if ((sh->free += len) < sdsavail(s)) { // 如果未使用空间足够容纳增加的空间  
        sh->len += len; // 更新字符串长度  
    } else { // 如果未使用空间不足,重新分配更大的内存空间  
        char *newptr;  
        size_t newlen = sh->len + len; // 计算新的字符串长度  
        newptr = zmalloc(newlen + sh->free); // 预分配额外的空间  
        memcpy(newptr, s, sh->len); // 将原有内容复制到新空间中  
        zfree(s); // 释放原有内存空间  
        sh = (struct sdshdr *) newptr;  
        sh->free = sh->free; // 更新未使用空间大小  
    }  
    return s; // 返回新的sds字符串指针  
}

在函数中,首先检查未使用空间是否足够容纳增加的空间。如果足够,则直接更新字符串长度;否则,重新分配更大的内存空间,并将原有内容复制到新空间中。最后,返回新的sds字符串指针。

总结来说,sds通过预分配机制和连续增长策略等优化手段,实现了高效的字符串操作。在源码中,这些优化策略体现在sdsMakeRoomFor和sdsIncrLen等函数中,通过合理的内存管理和数据结构的设计,提高了Redis中字符串操作的性能。
在这里插入图片描述

总结

  • Redis 的字符串表示为 sds ,而不是 C 字符串(以 \0 结尾的 char*)。
  • 对比 C 字符串,sds 有以下特性:
    • 可以高效地执行长度计算(strlen);
    • 可以高效地执行追加操作(append); – 二进制安全;
    • sds 会为追加操作进行优化:加快追加操作的速度,并降低内存分配的次数,代价是多占 用了一些内存,而且这些内存不会被主动释放。

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

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

相关文章

Tomcat 的 work 目录缓存导致的JSP页面图片更新问题

一、问题分析 1. 修改后重新部署没有变化 笔者之前部署了一个后台管理项目&#xff0c;通过它来发布课程内容&#xff0c;其中有一个 JSP 课程页面&#xff0c;在该 JSP 页面里也引用了类文件 Constant.java 里的一个变量&#xff08;ALIYUN_OSS_PATH&#xff09;&#xff0c;…

7个PyCharm实用插件实现轻松编程

大家好&#xff0c;IDE&#xff08;集成开发环境&#xff09;是开发者的武器&#xff0c;使用一个好的IDE和一些很棒的插件&#xff0c;工作效率会更高。Python是一种广泛使用的编程语言&#xff0c;PyCharm是最受欢迎的Python IDE之一。以下介绍7个PyCharm插件&#xff0c;它们…

JavaScript基础(25)_dom查询练习(二)

<!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>dom查询练习二</title><link rel"stylesheet" href"../browser_default_style/reset.css"><style>form {margi…

K8S--持久卷(PersistentVolume)的用法

原文网址&#xff1a;K8S--持久卷(PersistentVolume)的用法-CSDN博客 简介 本文介绍K8S的持久卷(PersistentVolume)的用法。 目标&#xff1a;用持久卷的方式将主机的磁盘与容器磁盘映射&#xff0c;安装nginx并运行。 --------------------------------------------------…

IO流-文件复制

IO流 概述&#xff1a;IO流&#xff0c;输入输出流&#xff08;Input Output&#xff09;流&#xff1a;一种抽象的概念&#xff0c;对数据传输的总称。&#xff08;数据在设备之间的传输称为流&#xff09;常见的功能 文件复制文件上传文件下载 学习流&#xff0c;我们要搞懂…

CSS 发光输入框动画

<template><view class="content"><input placeholder="请输入..." class="input" /> </view> </template><script></script><style>/* 设置整个页面的背景颜色为 #212121 */body{background-c…

centos7系统部署SqlServer2019

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 一 关于SQL Server SQL Server数据库是Microsoft开发设计的一个关系数据库智能管理系统(RDBMS)。 二 安装部署 2.1 安装依赖 …

使用 matlab 求解最小二乘问题

有约束线性最小二乘 其标准形式为&#xff1a; min ⁡ x 1 2 ∥ C x − d ∥ 2 2 \mathop {\min }\limits_x \quad \frac{1}{2}\left\| Cx-d \right\|_2^2 xmin​21​∥Cx−d∥22​ 约束条件为&#xff1a; A ⋅ x ≤ b A e q ⋅ x b e q l b ≤ x ≤ u b \begin{aligned} …

linux创建pyspark虚拟环境

一、创建虚拟环境 conda create -n test python3.6.6 二、注意添加镜像 vi /root/.condarc channels:- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/- http://mirrors.ustc.edu.cn/anaconda/pkgs/ma…

消息队列-RockMQ-批量收发实践

批量收发实战 发送消息是需要网络连接的如果我们单条发送吞吐量可能没有批量发送好。剖来那个发送可以减少网络IO开销&#xff0c;但是也不能一批次发送太多的数据&#xff0c;需要根据每条消息的大小和网络带宽来确定量的数目。 比如网络带宽为可以支持一次性发送8M的数据包&…

如何解读服务器的配置和架构?

在当今数字化时代&#xff0c;服务器作为企业或组织的重要基础设施&#xff0c;其配置和架构对于保障业务的稳定运行至关重要。如何解读服务器的配置和架构&#xff0c;成为了一个备受关注的话题。本文将围绕服务器配置和架构的解读进行深入探讨&#xff0c;帮助读者更好地理解…

DDIM学习笔记

写在前面&#xff1a; &#xff08;1&#xff09;建议看这篇论文之前&#xff0c;可先看我写的前一篇论文&#xff1a; DDPM推导笔记-大白话推导 主要学习和参考了以下文章&#xff1a; &#xff08;1&#xff09;一文带你看懂DDPM和DDIM &#xff08;2&#xff09;关于 DDIM …

如何精选WordPress插件

WordPress的强大功能大多得益于其众多插件。正确选择插件可以让你的网站功能强大、运行平稳&#xff0c;而错误的选择则可能导致网站变慢甚至出现安全漏洞。这篇文章将指导你如何在众多可选的插件中作出明智的选择。 明确需求 在浏览WordPress的插件目录或其他市场之前&#…

阶段十-分布式-任务调度

第一章 定时任务概述 在项目中开发定时任务应该一种比较常见的需求&#xff0c;在 Java 中开发定时任务主要有三种解决方案&#xff1a;一是使用JDK 自带的 Timer&#xff0c;二是使用 Spring Task&#xff0c;三是使用第三方组件 Quartz Timer 是 JDK 自带的定时任务工具,其…

STL标准库与泛型编程(侯捷)笔记1

STL标准库与泛型编程&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCo…

Vue2:脚手架Vue-CLI的使用

一、环境准备 vue脚手架&#xff08;vue-CLI&#xff09;的使用是基于nodejs环境下的。 你可以简单理解为&#xff0c;Java项目需要再jvm虚拟机上才能编译运行 nodejs的作用就是将vue文件编译成html、css、js代码文件。 如何安装nodejs 参考&#xff1a;https://blog.csdn.net…

谁动了我的注册表?免费的注册表对比分析工具

关于这款工具&#xff0c;可以在B站搜谁动了我的注册表&#xff0c;UP主名字为有限的未知。该注册表对比分析工具视频教程链接如下。谁动了我的注册表&#xff1f;注册表比对分析工具 & 手动实现右键菜单自由_哔哩哔哩_bilibili 声明&#xff1a;该款注册表分析软件&#…

想要成为机器学习领域的高手吗?这里有五本必读免费书,订阅周报发链接 (下)

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

接口测试实战教程详解(加密解密攻防)

一、对称加密 对称加密算法是共享密钥加密算法&#xff0c;在加密解密过程中&#xff0c;使用的密钥只有一个。发送和接收双方事先都知道加密的密钥&#xff0c;均使用这个密钥对数据进行加密和解密。 数据加密&#xff1a;在对称加密算法中&#xff0c;数据发送方将明文 (原…

用python提取excel表格第一列汉字首字母到第二列

今天有个任务就是需要提取excel表格里面的汉字首字母&#xff0c;然后我就手动写了三个小时&#xff0c;结果还剩3000多行&#xff0c;这样下去不行啊 想了下用python能不能做到呢&#xff1f; import openpyxl from pypinyin import lazy_pinyin, Style# 加载工作簿 workbook…