计算Redis容量,并不只是仅仅计算key占多少字节,value占多少字节,因为Redis为了维护自身的数据结构,也会占用部分内存,本文章简单介绍每种数据类型(StringHashSetZSetList)占用内存量,供做Redis容量评估时使用。当然,大多数情况下,key和value就是主要占用,能解大部分问题

在看这里之前,可以先看一下底层 - 数据结构 这篇文章

jemalloc内存分配规则

jemalloc是一种通用的内存管理方法,着重于减少内存碎片和支持可伸缩的并发性,做redis容量评估前必须对jemalloc的内存分配规则有一定了解。

jemalloc基于申请内存的大小把内存分配分为三个等级:small,large,huge:

  • Small Object 的size以8字节,16字节,32字节等分隔开,小于页大小;
  • Large Object 的size以分页为单位,等差间隔排列,小于chunk的大小;
  • Huge Object 的大小是chunk大小的整数倍。

对于64位系统,一般chunk大小为4M,页大小为4K,内存分配的具体规则如下:

jemalloc在分配内存块时会分配大于实际值的2^n的值,例如实际值时6字节,那么会分配8字节

数据类型 占用量
dicEntry 主要包括3个指针,key、value、哈希冲突时下个指针,耗费容量为8*3=24字节,jemalloc会分配32字节的内存块
dict结构 88字节,jemalloc会分配 96 字节的内存块
redisObject type(4bit)、encoding(4bit)、lru(24bit)、int(8byte)、ptr指针(8byte)。因此redisObject结构占用(4+4+24)/8 +4+8 = 16字节。
key_SDS key的长度 + 9,jemalloc分配 >= 该值的2^n的值
val_SDS value的长度 + 9,jemalloc分配 >= 该值的2^n的值
key的个数 所有的key的个数
bucket个数 大于key的个数的2^n次方,例如key个数是2000,那么bucket=2048
指针大小 8 byte

SDS中的主要包括两个表示长度int占用大小为8字节,redis中字符串还用“/0”表示结束占用1字节,所以 sds占用大小为9字节 + 数据长度

dict结构 这里会分配96 字节的内存块?为什么不是128?

内存划分

Redis内存占用主要可以划分为如下几个部分:

  • 数据:Redis数据占用内存dataset.bytes包括key-value占用内存、dicEntry占用内存、SDS占用内存等。

    数据所占内存 = 当前所占总内存total.allocated - 额外内存overhead.total

  • 初始化内存:redis启动初始化时使用的内存startup.allocated,属于额外内存overhead.total的一部分。

  • 主从复制内存:用于主从复制,属于额外内存一部分。

  • 缓冲区内存:缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。

  • 内存碎片:内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。

    内存碎片率 = Redis进程占用内存 / 当前所占内存total.allocated

    内存碎片涉及到内存碎片率fragmentation,该值对于查看内存是否够用比较重要:

    • 该值一般>1,数值越大,说明内存碎片越多。
    • 如果<1,说明Redis占用了虚拟内存,而虚拟内存是基于磁盘的,速度会变慢,所以如果<1,就需要特别注意是否是内存不足了。
    • 一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);

redis数据内存容量评估

redis容量评估模型根据key类型而有所不同。

string

一个简单的set命令最终会产生4个消耗内存的结构,中间free掉的不考虑:

  • 1个dictEntry结构,24字节,负责保存具体的键值对;
  • 1个redisObject结构,16字节,用作val对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个SDS结构,(val长度 + 9)字节,用作val字符串;

当key个数逐渐增多,redis还会以rehash的方式扩展哈希表节点数组,即增大哈希表的bucket个数,每个bucket元素都是个指针(dictEntry*),占8字节,bucket个数是超过key个数向上求整的2的n次方。

评估模型

真实情况下,每个结构最终真正占用的内存还要考虑jemalloc的内存分配规则,综上所述,string类型的容量评估模型为:

总内存消耗 = (dictEntry大小 + redisObject大小 +key_SDS大小 + val_SDS大小)×key个数 + bucket个数 ×指针大小

即:

总内存消耗 = (32 + 16 + key_SDS大小 + val_SDS大小)×key个数 + bucket个数 × 8

32是因为是24,但jemalloc会分配32字节的内存块

测试验证

string类型容量评估测试脚本如下:

#!/bin/sh

old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory" for((i=1000; i<3000; i++))
do
./redis-cli -h 0 -p 10009 set test_key_$i test_value_$i > /dev/null
sleep 0.2
done new_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory" let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 13,value长度为15,key个数为2000,根据上面总结的容量评估模型,容量预估值为2000 ×(32 + 16 + 32 + 32) + 2048× 8 = 240384

运行测试脚本,得到结果如下:

结果都是240384,说明模型预估的十分精确。

hash

哈希对象的底层实现数据结构可能是listpack或者hashtable,当同时满足下面这两个条件时,哈希对象使用listpack这种结构(此处列出的条件都是redis默认配置,可以更改):

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
  • 哈希对象保存的键值对的数量都小于512个;

可以看出,业务侧真实使用场景基本都不能满足这两个条件,所以哈希类型大部分都是hashtable结构,因此本篇文章只讲hashtable。

与string类型不同的是,hash类型的值对象并不是指向一个SDS结构,而是指向又一个dict结构,dict结构保存了哈希对象具体的键值对,hash类型结构关系如图所示:

一个hmset命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的哈希对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
  • 1个dict结构,88字节,负责保存哈希对象的键值对;
  • n个dictEntry结构,24×n 字节,负责保存具体的field和value,n等于field个数;
  • n个redisObject结构,16×n 字节,用作field对象;
  • n个redisObject结构,16×n 字节,用作value对象;
  • n个SDS结构,(field长度 + 9)× n字节,用作field字符串;
  • n个SDS结构,(value长度 + 9)× n字节,用作value字符串;

评估模型

因为hash类型内部有两个dict结构,所以最终会有产生两种rehash,一种rehash基准是field个数,另一种rehash基准是key个数,结合jemalloc内存分配规则,hash类型的容量评估模型为:

总内存消耗 = [(redisObject大小 ×2 +field_SDS大小 + val_SDS大小 + dictEntry大小)× field个数 + field_bucket个数× 指针大小 + dict大小 + redisObject大小 +key_SDS大小 + dictEntry大小 ] × key个数 + key_bucket个数 × 指针大小

即:

总内存消耗 = [(16 ×2 +field_SDS大小 + val_SDS大小 + 32)× field个数 + field_bucket个数× 8 + 96 + 16 +key_SDS大小 + 32 ] × key个数 + key_bucket个数 × 8

测试验证

hash类型容量评估测试脚本如下:

#!/bin/sh

value_prefix="test_value_123456789012345678901234567890123456789012345678901234567890_"

old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory" for((i=100; i<300; i++))
do
for((j=100; j<300; j++))
do
./redis-cli -h 0 -p 10009 hset test_key_$i test_field_$j $value_prefix$j > /dev/null
done
sleep 0.5
done new_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory" let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 12,field长度为14,value长度为75,key个数为200,field个数为200,根据上面总结的容量评估模型,容量预估值为[(16 + 16 + 32 + 96 + 32)×200 + 256×8 + 96 + 16 + 32 + 32 ]× 200 + 256× 8 = 8126848

运行测试脚本,得到结果如下:

结果相差40,说明模型预测比较准确。

zset

同哈希对象类似,有序集合对象的底层实现数据结构也分两种:listpack或者skiplist,当同时满足下面这两个条件时,有序集合对象使用ziplist这种结构(此处列出的条件都是redis默认配置,可以更改):

  • 有序集合对象保存的元素数量小于128个;
  • 有序集合保存的所有元素成员的长度都小于64字节;

业务侧真实使用时基本都不能同时满足这两个条件,因此这里只讲skiplist结构的情况。skiplist类型的值对象指向一个zset结构,zset结构同时包含一个字典和一个跳跃表,占用的总字节数为16,具体定义如下(redis.h/zset):

typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;

跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素,dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素,这两种数据结构会通过指针来共享相同元素的成员和分值,没有浪费额外的内存。zset类型的结构关系如图所示:

一个zadd命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的有序集合对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的zset结构;
  • 1个zset结构,16字节,负责保存下属的dict和zskiplist结构;
  • 1个dict结构,88字节,负责保存集合元素中成员到分值的映射;
  • n个dictEntry结构,24×n字节,负责保存具体的成员和分值,n等于集合成员个数;
  • 1个zskiplist结构,32字节,负责保存跳跃表的相关信息;
  • 1个32层的zskiplistNode结构,24+16×32=536字节,用作跳跃表头结点;
  • n个zskiplistNode结构,(24+16×m)×n字节,用作跳跃表节点,m等于节点层数;
  • n个redisObject结构,16×n字节,用作集合中的成员对象;
  • n个SDS结构,(value长度 + 9)×n字节,用作成员字符串;

因为每个zskiplistNode节点的层数都是根据幂次定律随机生成的,而容量评估需要确切值,因此这里采用概率中的期望值来代替单个节点的大小,结合jemalloc内存分配规则,经计算,单个zskiplistNode节点大小的期望值为53.336。

评估模型

zset类型内部同样包含两个dict结构,所以最终会有产生两种rehash,一种rehash基准是成员个数,另一种rehash基准是key个数,zset类型的容量评估模型为:

总内存消耗 = [(val_SDS大小 + redisObject大小 + zskiplistNode大小 + dictEntry大小)×value个数 +value_bucket个数 ×指针大小 + 32层zskiplistNode大小 + zskiplist大小 + dict大小 + zset大小 + redisObject大小 + key_SDS大小 + dictEntry大小 ] ×key个数 +key_bucket个数 × 指针大小

即:

总内存消耗 = [(val_SDS大小 + 16 + 53.336 + 32)×value个数 +value_bucket个数 × 8 + 640 +32 + 96 + 16 + 16 + key_SDS大小 + 32 ] ×key个数 +key_bucket个数 × 8

测试验证

zset类型容量评估测试脚本如下:

#!/bin/sh

value_prefix="test_value_123456789012345678901234567890123456789012345678901234567890_"

old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory" for((i=100; i<300; i++))
do
for((j=100; j<300; j++))
do
./redis-cli -h 0 -p 10009 zadd test_key_$i $j $value_prefix$j > /dev/null
done
sleep 0.5
done new_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory" let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 12,value长度为75,key个数为200,value个数为200,根据上面总结的容量评估模型,容量预估值为[(96 + 16 + 53.336 + 32)×200 + 256×8 + 640 + 32 + 96 + 16 + 16 + 32 + 32 ] ×200 + 256 × 8 = 8477888

运行测试脚本,得到结果如下:

结果相差672,说明模型预测比较准确。

list

列表对象的底层实现数据结构同样分两种:listpack或者linkedlist,当同时满足下面这两个条件时,列表对象使用listpack这种结构(此处列出的条件都是redis默认配置,可以更改):

  • 列表对象保存的所有字符串元素的长度都小于64字节;
  • 列表对象保存的元素数量小于512个;

因为实际使用情况,这里同样只讲linkedlist结构。linkedlist类型的值对象指向一个list结构,具体结构关系如图所示:

一个rpush或者lpush命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的列表对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的list结构;
  • 1个list结构,48字节,负责管理链表节点;
  • n个listNode结构,24×n字节,n等于value个数;
  • n个redisObject结构,16×n字节,用作链表中的值对象;
  • n个SDS结构,(value长度 + 9)×n字节,用作值对象指向的字符串;

评估模型

list类型内部只有一个dict结构,rehash基准为key个数,综上,list类型的容量评估模型为:

总内存消耗 = [(val_SDS大小 + redisObject大小 + listNode大小)× value个数 + list大小 + redisObject大小 + key_SDS大小 + dictEntry大小 ] × key个数 + key_bucket个数 × 指针大小

即:

总内存消耗 = [(val_SDS大小 +16 + 32)× value个数 + 16 + 32 + key_SDS大小 + 32 ] × key个数 + key_bucket个数 × 8

测试验证

list类型容量评估测试脚本如下:

#!/bin/sh

value_prefix="test_value_123456789012345678901234567890123456789012345678901234567890_"

old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory" for((i=100; i<300; i++))
do
for((j=100; j<300; j++))
do
./redis-cli -h 0 -p 10009 rpush test_key_$i $value_prefix$j > /dev/null
done
sleep 0.5
done new_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory" let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 12,value长度为75,key个数为200,value个数为200,根据上面总结的容量评估模型,容量预估值为[(96 + 16 + 32) ×200 + 48 + 16 + 32 + 32 ] × 200 + 256 ×8 = 5787648

运行测试脚本,得到结果如下:

结果都是5787648,说明模型预估的十分精确。

Set

一个sadd命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的set对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
  • 1个dict结构,88字节,负责保存哈希对象的键值对;
  • n个dictEntry结构,24×n 字节,负责保存具体的member,n等于member个数;
  • n个redisObject结构,16×n 字节,用作member对象;
  • n个SDS结构,(field长度 + 9)× n字节,用作member字符串;

评估模型

set与hash类似,只是value部分没有具体的值。与hash类型一样,内部有两个dict结构,所以最终会有产生两种rehash,一种rehash基准是member个数,另一种rehash基准是key个数,结合jemalloc内存分配规则,hash类型的容量评估模型为:

总内存消耗 = [(redisObject大小 +member_SDS大小 + dictEntry大小)× member个数 + member_bucket个数× 指针大小 + dict大小 + redisObject大小 +key_SDS大小 + dictEntry大小 ] × key个数 + key_bucket个数×指针大小

即:

总内存消耗 = [(16 +member_SDS大小 + 32)× member个数 + member_bucket个数× 8 + 96 + 16 +key_SDS大小 + 32 ] × key个数 + key_bucket个数×8

Redis容量评估模型的更多相关文章

  1. 深入了解一下Redis的内存模型!

    一.前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分. 我们使用Redis时,会接触Redis的5种对象类型(字 ...

  2. redis的线程模型是什么?

    1.面试题 redis和memcached有什么区别? redis的线程模型是什么? 为什么单线程的redis比多线程的memcached效率要高得多(为什么redis是单线程的但是还可以支撑高并发) ...

  3. 【数学建模】day14-建立GM(1,1)预测评估模型应用

    学习建立GM(1,1)灰色预测评估模型,解决实际问题: SARS疫情对某些经济指标的影响问题 一.问题的提出 2003 年的 SARS 疫情对中国部分行业的经济发展产生了一定影响,特别是对部分 疫情较 ...

  4. MySQL准入规范及容量评估

    一.数据库设计 1.表结构设计 -表中的自增列(auto_increment属性)推荐使用bigint类型 -首选使用非空的唯一键, 其次选择自增列或发号器 不使用更新频繁的列,尽量不选择字符串列,不 ...

  5. scikit-learn 中常用的评估模型

    一,scikit-learn中常用的评估模型 1.评估分类模型: ​ 2.评估回归模型: ​ 二.常见模型评估解析: •对于二分类问题,可将样例根据其真实类别和分类器预测类别划分为:(T,F表示预测的 ...

  6. mysql 性能容量评估

    性能容量评估   分析上线业务场景 评估数据库服务器所需性能指标 预估可能成为瓶颈的服务器资源 帮助数据库性能调优   数据库服务器硬件性能指标项: 磁盘IO性能 内存容量 CPU 网络吞吐量 磁盘容 ...

  7. Spark Mllib里决策树回归分析使用.rootMeanSquaredError方法计算出以RMSE来评估模型的准确率(图文详解)

    不多说,直接上干货! Spark Mllib里决策树二元分类使用.areaUnderROC方法计算出以AUC来评估模型的准确率和决策树多元分类使用.precision方法以precision来评估模型 ...

  8. Spark Mllib里决策树二元分类使用.areaUnderROC方法计算出以AUC来评估模型的准确率和决策树多元分类使用.precision方法以precision来评估模型的准确率(图文详解)

    不多说,直接上干货! Spark Mllib里决策树二元分类使用.areaUnderROC方法计算出以AUC来评估模型的准确率 具体,见 Hadoop+Spark大数据巨量分析与机器学习整合开发实战的 ...

  9. 2.redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?

    作者:中华石杉 面试题 redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发? 面试官心理分析 这个是问 redis 的时候,最基本的 ...

  10. redis和memcached有什么区别?redis的线程模型是什么?为什么单线程的redis比多线程的memcached效率要高得多(为什么redis是单线程的但是还可以支撑高并发)?

    1.redis和memcached有什么区别? 这个事儿吧,你可以比较出N多个区别来,但是我还是采取redis作者给出的几个比较吧 1)Redis支持服务器端的数据操作:Redis相比Memcache ...

随机推荐

  1. 可以安装成功的red5资源

    找了好久,只有这个可用 https://download.csdn.net/download/haiwalt/8600781 注意下 RED5_HOME 路径是运行exe文件后   有个  D:\Re ...

  2. MySQL 03 事务隔离:为什么你改了我还看不见?

    事务就是要保证一组数据库操作,要么全部成功,要么全部失败.在MySQL中,事务支持是在引擎层实现的,这也是InnoDB取代MyISAM的重要原因之一. 隔离性与隔离级别 事务的四大特性:原子性.一致性 ...

  3. von Mises Distribution (冯·米赛斯分布)的随机模拟与参数估计的笔记(二)

    von Mises Distribution (冯·米赛斯分布)的随机模拟与参数估计的笔记(二) 1.参数估计算子分析 ​ 在上一节中,我们讨论了von Mises Distribution的概率分布 ...

  4. nginx反向代理,负载均衡和yeauty集成的websocket的使用

    被要求一个这样的需求:要求项目和websocket使用一个端口.经过一周激烈争论,领导终于同意可以可以开通一个端口,一个月了,端口还没有开. 正式环境已经通过此方法进行部署,没有问题. 前言 因涉及到 ...

  5. MySQL的三大日志

    前言 飞机失事靠黑匣子还原真相,MySQL崩溃靠三大日志保障数据安全. 作为一个工作多年的程序员,我见过太多因日志配置不当引发的灾难:数据丢失.主从同步中断.事务回滚失败... 今天,我将用最通俗的方 ...

  6. R Studio操作技巧笔记

    快捷键 Ctrl+Shift+C 注释快捷键,可以添加/消除注释,也可多行注释 Ctrl + Shift + Enter 执行整个文件 Ctrl+Enter 运行当前/被选中的代码 Ctrl+L 清空 ...

  7. 向量数据库 Chroma 和 Milvus的使用

    一.什么是向量数据库? 向量数据库(Vector Database)是专门用来存储和检索向量数据的数据库.它广泛应用于图像搜索.推荐系统.自然语言处理等领域. 简单理解: 你给数据库一堆「特征向量」( ...

  8. killall bluetoothd 一直不成功

    最后调查发现是bluetman 惹得祸 贴上他的守护程序,以后也可以写一个 #! /usr/bin/python3 from __future__ import print_function from ...

  9. libsvm matlab 上的安装

    简介windows上matlab安装还是有一些坑的 首先 matlab2016a 安装一个 编译器 tdm64-gcc-4.9.2.exe 然后更改 libsvm 中的matlab make.m 重点 ...

  10. 运用ETL工具,实现慧穗云数据管理

    在数字化时代,数据已成为企业发展的核心驱动力.然而,许多企业在面对庞大的数据量和多样的数据源时,往往面临着数据整合和转换的难题.为了解决这一问题,慧穗云与ETL工具(Extract, Transfor ...