从源码看redis写入一个key需要多大的内存
mhr18 2024-11-16 23:29 25 浏览 0 评论
上一篇在说redis的bit位操作时候,有一个同学在评论区问到如果mysql有一个g的数据,全部加载到redis需要多大的内存?本文就来一起探讨一下redis中数据是如何存储的,使用内存又是如何计算的,力求讲清楚以下几点内容:
- 从源码看redis的字典
- redis写入一个key,内存增加了多少?
- 使用redis-benchmark压测看redis内存变化,掌握预估内存的办法
本文基于如下环境:
操作系统:Mac OS
版本:Redis 5.0.7 (00000000/0) 64 bit
运行模式:Running in standalone mode
文章内容较长,建议大家收藏后持续阅读~点击右上方关注,阅读更多技术文章!
redis字典
说起redis的数据结构,字典是最底层的数据结构了。《redis设计与实现》一书中对字典的定义:
字典,又称为符号表(Symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构。
redis构建了自己的字典实现,redis中的数据库就是使用字典来作为底层实现的,redis中的哈希键(Hash)也使用字典来实现的。
而redis的字典又是使用哈希表来作为底层实现的。哈希算法采用的是MurmurHash2算法,一个优秀的哈希算法有如下要求:
- 雪崩效应(任何输入的微小变化都会导致巨大的差异)
- 低碰撞率
- 高性能
关于Murmurhash算法详情以及实际应用可阅读我的文章:MurmurHash算法及应用场景
在安装的redis/src文件夹下可以看到有很多后缀名为.h、.c、.o的文件,其中.h代表的是.c文件中用到的变量、数组、函数的声明,.c文件是.h文件中声明的变量、数组、函数具体的定义,而.o就是编译后的汇编文件。
大家可以看到有dict.h文件,这个文件里面即定义了字典的数据结构,我们打开源码可以看到如下四个C语言的结构体(struct):
typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; } dictEntry;
typedef struct dictType { uint64_t (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType;
typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht;
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ unsigned long iterators; /* number of iterators currently running */ } dict;
用一张图来表述他们之间的关系如下:
当我们执行一条如下语句的时候:
set testKey testValue
如果是首次redis写入,会创建一个dict字典对象,字典对象的数据如下:
当然如果你写入的不是字符串类型的数据类型,而是List、Hash、Set、ZSet四种数据,也和上图的数据结构一样,只是dictEntry里面的值对象*val指针会指向不同的对象,不同的对象会有不同的数据结构,强烈推荐大家阅读《redis设计与实现》这本书,深读此书将会彻底搞清楚redis。
redis内存计算
上节从redis的字典说了redis的底层数据结构是如何保存我们写入的key的,那么当我们执行命令写入key到redis中,redis的内存具体是如何分配的呢?我们一起来实验一下:
首先执行FLUSHALL命令来清空我们的redis,保证没有其他key干扰,然后执行:
src/redis-cli info | grep mem
获取redis初始内存信息:
关键属性说明如下(更多属性说明请查阅redis官网):
redis初始占用内存:1039472字节,当我们执行:
set testKey testValue
再查看内存变化为:
也就是说上面的语句执行后吃了redis内存为:1057472-1039472=18000b=17.58K,那是不是代表上面的执行吃了18K的内存呢?
我们再写入一个key:
set testKey1 testValue1
通过上文对字典的描述可以知道testKey1在redis中的存储应该如下图所示:
查看内存变化为:
used_memory:1057552
才发现吃了80字节的内存。
所以我们可以知道的是redis启动之后需要占用一部分内存,这部分内存1039472字节用于redis服务的运行以及初始化一些数据。另外首次写入redis的key之后,需要构造上文所说的redis字典结构,因此需要占用一些内存。
我们需要知道的是当我们写入一个key的时候占用的内存到底是多少,由于我们写的值都没有超过44个字节,所以采用EMBSTR数据结构存储。所以我们可以查看object.c源码里面是如何创建对象的:
分配内存的代码:
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
可以看到redis为我们分配了:
sizeof(robj)+sizeof(struct sdshdr8)+len+1
这么大的内存,其中的robj代表的是redisObject,查看server.h中关于redisObject对象的定义:
因此sizeof(robj) = 16字节。
sdshdr8即上图中的sdshdr中的头部3个字节。
因此testValue1这个采用EMBSTR编码的存储需要内存:16+3+10+1=30字节,redis内存分配器为其分配32字节。
我们再来计算testKey1占用的内存,testKey1存储的就是一个SDS简单动态对象,少了robj的内存占用,因此需要内存:3+8+1 = 12字节,redis分配器为其分配16字节。
总共需要内存为32+16=48字节,那为什么占用的是80字节呢?剩下的32字节谁吃了呢?大家不要忘记了dictEntry这个结构还有三个指针呢:
三个指针占用内存:3*8-24字节,jemalloc会为其分配32个字节。
至此,我们便能清晰的知道当我们执行一个字符串对象(字符串长度不超过44!)写入的时候,需要占用内存多少了。
即80-18(testKey1&testValue1) = 62的长度。但是我们需要知道这62个长度都吃在什么地方了。
上面说的是当写入String类型的数据且长度值不超过44的时候占用的内存计算方法。其他数据类型如List、Hash、Set、Zset大家可以参考我上面的方法和思路并查看相关redis源码以及redis技术资料即可得知。
redis-benchmark压测
src目录下redis-benchmark是redis自带的压测工具,压测语法格式:
redis-benchmark [option] [option value]
option可选参数如下:
执行压测语句:
src/redis-benchmark -p 6379 -t set -c 100 -n 1000000 -r 1000000
输出压测结果:
? redis-5.0.7 src/redis-benchmark -p 6379 -t set -c 100 -n 1000000 -r 1000000 ====== SET ====== 1000000 requests completed in 20.04 seconds 100 parallel clients 3 bytes payload keep alive: 1 44.04% <= 1 milliseconds 96.99% <= 2 milliseconds 98.73% <= 3 milliseconds 99.29% <= 4 milliseconds 99.53% <= 5 milliseconds 99.68% <= 6 milliseconds 99.76% <= 7 milliseconds 99.81% <= 8 milliseconds 99.85% <= 9 milliseconds 99.90% <= 10 milliseconds 99.92% <= 11 milliseconds 99.93% <= 12 milliseconds 99.94% <= 13 milliseconds 99.95% <= 14 milliseconds 99.96% <= 15 milliseconds 99.96% <= 16 milliseconds 99.96% <= 17 milliseconds 99.97% <= 18 milliseconds 99.97% <= 19 milliseconds 99.97% <= 20 milliseconds 99.97% <= 21 milliseconds 99.98% <= 22 milliseconds 99.98% <= 23 milliseconds 99.98% <= 24 milliseconds 99.98% <= 25 milliseconds 99.98% <= 26 milliseconds 99.98% <= 27 milliseconds 99.98% <= 28 milliseconds 99.98% <= 31 milliseconds 99.98% <= 32 milliseconds 99.98% <= 33 milliseconds 99.99% <= 34 milliseconds 99.99% <= 35 milliseconds 99.99% <= 36 milliseconds 99.99% <= 37 milliseconds 99.99% <= 38 milliseconds 100.00% <= 39 milliseconds 100.00% <= 41 milliseconds 100.00% <= 50 milliseconds 100.00% <= 58 milliseconds 100.00% <= 58 milliseconds 49907.67 requests per second
压测完毕后执行src/redis-cli info | grep mem命令查看内存占用情况:
共占用内存:70084048-1039472=69044576字节=65.85M
总共写入631833个key,每个key的内容格式如下:
set key:000000075890 xxx
即每个key占用内存为:32+32+32=96字节,共消耗:631833*96=57.85M,我们压测的info总共消耗65.85M,还差8M去哪里了呢?
还记得第一部分说的字典结构里面的ht[0]和ht[1]么?初始ht[0]为4,分配的内存就是4*8b=32b,当需要存储的数据超过4个的时候就会触发rehash动作,将ht[1]扩容为ht[0]的2倍,然后将h[0]里的数据全部rehash至ht[1],再互相交换一下,ht[1]变成ht[0],ht[0]变成ht[1]。那么当我们写入的631833个key将会产生rehash多少次呢?
realsize=4 realsize=8 realsize=16 realsize=32 realsize=64 realsize=128 realsize=256 realsize=512 realsize=1024 realsize=2048 realsize=4096 realsize=8192 realsize=16384 realsize=32768 realsize=65536 realsize=131072 realsize=262144 realsize=524288 realsize=1048576
所以目前realsize是1048576,那么总共需要分配的内存就是1048576*8= 8388608,8388608/1024/1024=8MB,刚好和我们压测的结果对上了!
总结
以上就是redis关于内存分配的相关知识了。上面只是对redis的字符串类型的数据进行解说,通过对字符串类型的部分源码解读我们可以清楚的知道一个key的写入到redis需要多大的内存。其他的数据结构这里没有做详细说明,但其实思路是一致的。让我们再看一下下图dictEntry对象的定义,从字典开始,前面的都一致,只是dictEntry里面的*val指向不同而已。
今天关于redis的内存分配相关知识就到这里了,我们下篇再见。欢迎关注我,持续阅读更多技术干货文章!
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)