Redis EmbeddedString
前言
Redis 写入键值对时,首先会先创建一个 RedisObject 对象来存储 Value。
如果写入的 Value 是字符串,那么 Redis 会再根据写入的字符串长度,来创建对应的 sdshdr 来存储字符串,最后把 RedisObject 的 ptr 指针指向 sdshdr。
我们来分析下这个过程,首先创建 RedisObject 需要分配一次内存,创建 sdshdr 又需要再分配一次内存。
由此可见,如果 RedisObject 和 sds 分开存储的话,需要多分配一次内存,内存碎片化的概率也会增加。
Redis 本着节省内存的原则,还可以做出哪些优化呢?
EmbeddedString
先回顾一下 RedisObject 结构,前三个属性合计占用 4 字节,refcount 占用 4 字节,ptr 指针占用 8 字节,合计 16 字节。
typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS;int refcount;void *ptr;
} robj;
Redis 默认使用 jemalloc 内存分配器,分配的内存必须是 2 的幂次方大小,比如你要申请 5 字节,jemalloc 会给你分配 8 字节;你要申请 10 字节,jemalloc 会分配 16 字节。
基于这个规则,Redis 就想,能不能创建 RedisObject 的同时就分配多一点内存,好存储接下来的字符串呢?当然可以,那申请多大合适呢?首先肯定要是 2 的幂次方数,32 字节有点太小了,因为 sdshdr8 头部就占用了 3 字节,再加上一个 ‘\0’ 结尾符,真正留给字符串的空间就剩 12 字节了,显然不实用,很容易溢出。
32 不够,那只能再往上加了,64 字节,可以存储 44 字节的字符串,基本够用了。恰巧在 x86 架构下,CPU 缓存行的大小一般也是 64 字节,刚好可以完整加载。
所以,现在我们得出一个结论,如果写入的字符串长度在 44 以内,那么就可以在创建 RedisObject 时直接申请 64 字节,然后把 sds 直接挨着 RedisObject 末尾写入,这样就可以避免再分配一次内存,内存的碎片率也能得到优化。
我们看看 Redis 具体是怎么做的,创建字符串对象的方法是createStringObject():
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);
}
常量 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 的值刚好就是 44,这证明了我们的猜想。如果字符串长度超过了 44,Redis 也只能分配 sds 空间,单独存储字符串了,对应的方法是createRawStringObject()。
这种和 RedisObject 存储在一起的字符串,Redis 给它取名叫 EmbeddedString,创建的方法是createEmbeddedStringObject():
robj *createEmbeddedStringObject(const char *ptr, size_t len) {robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1); // sh 指向 RedisObject末尾 即sdshdr开始位置o->type = OBJ_STRING; // 对外类型还是 stringo->encoding = OBJ_ENCODING_EMBSTR; // 区别于普通sds,这里的编码类型是8o->ptr = sh+1; // ptr 指向sdshdr末尾 即字符串开始位置o->refcount = 1;// 设置lru时钟if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;} else {o->lru = LRU_CLOCK();}// 设置sdshdr头sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr == SDS_NOINIT)sh->buf[len] = '\0';else if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o;
}
创建 EmbeddedString 的步骤如下:
- 先分配内存,大小是 RedisObject 大小 + sdshdr8 大小 + 字符串长度 + 1个’\0’字符的长度
- sh 指针指向 sdshdr 的起始位置
- RedisObject->ptr 指针指向字符数组的起始位置,在介绍 sds 的说过了,指针左移一位就能读到 flags
- 给 RedisObject 对象设置 lru 时间戳
- 设置 sdshdr 头数据
尾巴
当我们向 Redis 写入 string 数据时,Redis 首先要创建 RedisObject 分配一次内存,然后再创建 sds 时又要二次分配内存,这样不仅浪费内存,还会增加碎片化率。Redis 结合 jemalloc 的分配策略,以及 x86 架构下的缓存行大小,决定如果写入的字符串长度较小,就一次直接申请 64 字节的内存,剩下 44 字节的长度用来存储字符串,这种字符串的存储方式也被称作 嵌入式字符串。
相关文章:
Redis EmbeddedString
前言 Redis 写入键值对时,首先会先创建一个 RedisObject 对象来存储 Value。 如果写入的 Value 是字符串,那么 Redis 会再根据写入的字符串长度,来创建对应的 sdshdr 来存储字符串,最后把 RedisObject 的 ptr 指针指向 sdshdr。 …...
SpringMVC之WEB-INF下页面跳转@ModelAttributeIDEA tomcat控制台中文乱码问题处理
WEB-INF下页面跳转 ModelAttribute来注解非请求处理方法 用途:预加载数据,会在每个RequestMapping方法执行之前调用。 特点:无需返回视图,返回类型void IDEA tomcat控制台中文乱码问题处理 复制此段代码:-Dfile.e…...
利用ChatGPT练习口语
目录 ChatGPT 这两天发布了一个激动人心的新功能,App端(包括iOS和Android)开始支持语音对话以及图片识别功能。 这两个功能一如既往的优先开放给Plus用户使用,现在将App更新到最新版本,就能体验。 为什么说激动人心&a…...
【Django 01】环境搭配与项目配置
1. 介绍 https://github.com/Joe-2002/sweettalk-django4.2#readme Django 是一个使用 Python 编写的开源 Web 应用程序框架,它提供了一套用于快速开发安全、 可扩展和高效的 Web 应用程序的工具和功能。Django 基于 MVC(Model-View-Controller…...
PyCharm配置运行参数
...
ChatGPT AIGC 实现Excel 交叉查找 Index+match 函数
行与列交叉多条件查找需求如下: 这个需求要使用Excel中最经典的组合函数Index+match函数。 函数公式可以交给ChatGPT AIGC来实现。 Prompt: 有一个表格A列为品牌,B列为月份,C列为销量,61行数据,请写出Excel函数公式根据E3单元格的品牌与F2单元格的月份查找对应的销量,…...
【前端学习】—多种方式实现数组拍平(十一)
【前端学习】—多种方式实现数组拍平(十一) 一、数组拍平 数组拍平也叫数组扁平化、数组拉平、数组降维,指的是把多维数组转化为一维数组。 二、使用场景 复杂场景下的数据处理(echarts做大屏数据展示) 三、如何实…...
智慧远程医疗服务:从零开始搭建互联网医院APP
互联网医院APP作为远程医疗服务的一部分,正在为患者和医生带来更便捷的医疗体验。本文将探讨如何从零开始构建一个互联网医院APP,包括关键步骤、技术要点和挑战。 一、确定项目目标和范围 在开始之前,您需要明确定义您的互联网医院APP的目标…...
ADAS可视化系统,让自动驾驶更简单 -- 入门篇
随着车载芯片的升级、技术的更新迭代,可视化ADAS逐渐变成汽车的标配走入大家的生活中,为大家的驾车出行带来切实的便捷。那么你了解HMI端ADAS的实现过程吗?作为ADAS可视化系统的入门篇,就跟大家聊一聊目前较常见的低消耗的一种ADA…...
探索低代码技术
低/无代码的高速发展,属于软件市场的选择,相较于传统编写代码的开发方式,低/无代码开发效率高、投入成本低、技术门槛也更低,未来更多软件应用将使用低/无代码技术完成,这也是趋势。 身为开发人员经常需要花大量时间在…...
头歌的数据库的第二次作业的答案
目录 MySQL-视图 第1关:创建所有保险资产的详细记录视图 第2关:基于视图的查询 MySQL数据库 - 连接查询 第1关:内连接查询 第2关:外连接查询 第3关:复合条件连接查询 MySQL数据库 - 子查询 第1关:…...
基于R329 SOC智能音响开发编译环境搭建
R329智能音响开发编译环境搭建 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务, R329编译命令 source build/envsetup.sh lunch make -j4 pack 编译工程选择 baidu_panshan...
libplctag开源库的API介绍
文章目录 1 开源库概要2 API介绍2.1 Tag Model(标签模型)2.2 Status Codes(状态码)2.3 Versions and Checking Library Compatibility(版本和检查库的兼容性)2.4 Tag Life Cycle(标签生命周期&a…...
智能化安全巡更巡查系统—提升安全管理效率
传统的巡检都是手工完成,记录、拍照,回到办公室打印表单再交给作业队伍整改,再去现场核实复查,流程繁琐,效率低。而且大部分工地为了减少麻烦,人员往往都是口头沟通,存在很大质量风险࿰…...
SAP MM学习笔记36 - 释放支付保留的发票
SAP中,请求书照合之后,发现不一致,就会支付保留。 支付保留,可以参考如下文章。 SAP MM学习笔记34 - 请求书照合中的支付保留(发票冻结)_东京老树根的博客-CSDN博客 当然发现不一致之后,如果不…...
MySQL数据库的ID列添加索引
要为MySQL数据库的ID列添加索引,可以使用以下语法: ALTER TABLE table_name ADD INDEX index_name (id);其中,table_name是要添加索引的表名,index_name是索引的名称,id是要添加索引的列名。 例如,如果要…...
LuaJIT编写的解析十六进制数据
以下是使用LuaJIT编写的解析十六进制数据并将uint16转换为JSON的示例代码: local ffi require("ffi") local bit require("bit") local cjson require("cjson")-- 定义结构体 ffi.cdef[[typedef struct {uint16_t value;} uint16…...
【SA8295P 源码分析 (一)】09 - XBL Loader 加载 QSEE、SEC、CPUCPFW、QHEE、APPSBL过程分析
【SA8295P 源码分析】09 - XBL Loader 加载 QSEE、SEC、CPUCPFW、QHEE、APPSBL过程分析 一、QSEE二、SEC三、CPUCPFW四、QHEE五、APPSBL系列文章汇总见:《【SA8295P 源码分析 (一)】系统部分 文章链接汇总 - 持续更新中》 本文链接:《【SA8295P 源码分析 (一)】09 - XBL Load…...
封装一个Element-ui生成一个可行内编辑的表格(vue2项目)
这个封装的是一个供整个项目使用的表格,可多次复用.放在一个全局使用的公共组件文件下. 大致功能介绍,封装自定义指令,点击获得焦点,显示输入框,失去焦点显示文本内容,类型是字典决定类型,图片可以显示图片名还是上传图片 子组件 <script> export default {props: {//生…...
hanniman 1v1 咨询
一共4种可选方案,3个To C(面向AI产品经理的职业规划诊断、求职内推套餐、模拟面试),1个To B(面向AI企业/投资机构/券商等)。 方案A:职业规划诊断 适合人群:AI产品经理 or 想转型A…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
