Redis4.0内存容量评估

文章目录

  • Redis容量评估
    • Redis内存模型
      • 查看内存占用
      • 内存划分
    • Redis数据内存
      • Redis数据内存分配
      • Redis数据内存计算
        • String
        • Hash
        • SortedSet
        • List
    • 其他内存命令
      • memory usage
      • memory doctor
      • memory purge

Redis容量评估

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

Redis内存模型

要评估Redis占用内存量,首先需要了解Redis的内存模型。通过Redis的内存模型,才能不仅知其然,而且知其所以然。

查看内存占用

首先在redis-cli命令行中键入memory stats命令查看下Redis内存占用,大概如下图所示:

 1) "peak.allocated" #Redis启动到现在,占用内存的峰值
 2) (integer) 850160
 3) "total.allocated" #当前使用的内存总量,Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即swap)
 4) (integer) 827608
 5) "startup.allocated" #Redis启动完成使用的内存字节数
 6) (integer) 765616
 7) "replication.backlog" #主从复制backlog使用的内存,默认10MB,backlog只在主从断线重连时发挥作用,主从复制本身并不依赖此项。
 8) (integer) 0
 9) "clients.slaves" #主从复制中所有slave的读写缓冲区
10) (integer) 0
11) "clients.normal" #除slave外所有其他客户端的读写缓冲区
12) (integer) 49630
13) "aof.buffer" #此项为aof持久化使用的缓存和aofrewrite时产生的缓存之和,如果关闭了appendonly那这项就一直为0 
14) (integer) 0 
15) "db.0"  #redis每个db的元信息使用的内存,这里只使用了db0,所以只打印了db0的内存使用状态
16) 1) "overhead.hashtable.main"
    2) (integer) 72
    3) "overhead.hashtable.expires"
    4) (integer) 0
17) "overhead.total" #redis额外的总开销内存字节数; 即分配器分配的总内存total.allocated,减去数据实际存储使用内存:startup.allocated+replication.backlog+clients.slaves+clients.normal+aof.buffer+dbx
18) (integer) 815318
19) "keys.count" #redis当前存储的key总量
20) (integer) 1
21) "keys.bytes-per-key" #平均每个key的内存大小:(total.allocated - startup.allocated) / keys.count
22) (integer) 61992
23) "dataset.bytes" #所有数据所使用的内存:total.allocated - overhead.total
24) (integer) 12290
25) "dataset.percentage" #所有数据占比:100 * dataset.bytes / (total.allocated - startup.allocated)
26) "19.5934348106384277"
27) "peak.percentage" #当前使用内存与历史最高值比例
28) "97.6251003742218018"
29) "fragmentation" #内存碎片比率
30) "2.1039986610412598"

总上图我们可以看出,Redis占用的内存并不仅仅包括数据内存dataset.bytes,还包括启动初始化内存、主从复制占用内存、缓冲区内存等。

内存划分

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

  • 数据
    Redis数据占用内存dataset.bytes包括key-value占用内存、dicEntry占用内存、SDS占用内存等。
    数 据 所 占 内 存 = 当 前 所 占 总 内 存 ‘ t o t a l . a l l o c a t e d ‘ − 额 外 内 存 ‘ o v e r h e a d . t o t a l ‘ 数据所占内存=当前所占总内存`total.allocated`-额外内存`overhead.total` =total.allocatedoverhead.total
  • 初始化内存
    redis启动初始化时使用的内存startup.allocated,属于额外内存overhead.total的一部分。
  • 主从复制内存
    用于主从复制,属于额外内存一部分。
  • 缓冲区内存
    缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。
  • 内存碎片
    内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。
    内 存 碎 片 率 = R e d i s 进 程 占 用 内 存 / 当 前 所 占 内 存 ‘ t o t a l . a l l o c a t e d ‘ 内存碎片率=Redis进程占用内存/当前所占内存`total.allocated` =Redis/total.allocated
    内存碎片涉及到内存碎片率fragmentation,该值对于查看内存是否够用比较重要:

该值一般>1,数值越大,说明内存碎片越多。如果<1,说明Redis占用了虚拟内存,而虚拟内存是基于磁盘的,速度会变慢,所以如果<1,就需要特别注意是否是内存不足了。
一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);上面截图中的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。

Redis数据内存

在了解了Redis内存模型之后,我们可以知道,Redis所占内存不仅仅时key-value,很包括其他内存占用。现在我们就来了解下Redis数据内存是如何占用的。

Redis数据内存分配

Redis数据内存除了包括key-value,还包括dicEntryredisObjectSDS等。

  • dicEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。
  • key:key的值并不是直接以字符串存储,而是存储在SDS结构中。
  • redisObject:value的值既不是直接以字符串存储,也不是像Key一样直接存储在SDS中,而是存储在redisObject中。
  • SDS:Redis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。

Redis数据内存计算

String

公式:
总 内 存 消 耗 = ( d i c t E n t r y 大 小 + r e d i s O b j e c t 大 小 + k e y S D S 大 小 + v a l S D S 大 小 ) × k e y 个 数 + b u c k e t 个 数 × 指 针 大 小 总内存消耗 = (dictEntry大小 + redisObject大小 +key_SDS大小 +val_SDS大小)×key个数 + bucket个数 ×指针大小 =dictEntry+redisObject+keySDS+valSDS×key+bucket×
公式:
单 个 内 存 消 耗 = d i c t E n t r y 大 小 + r e d i s O b j e c t 大 小 + k e y S D S 大 小 + v a l S D S 大 小 单个内存消耗=dictEntry大小 + redisObject大小 +key_SDS大小 +val_SDS大小 =dictEntry+redisObject+keySDS+valSDS
jemalloc在分配内存块时会分配大于实际值的2n的值,例如实际值时6字节,那么会分配8字节

数据类型 占用量
dicEntry 24字节,jemalloc会分配32字节的内存块
redisObject 16字节
key_SDS key的长度+9,jemalloc分配>=该值的2n的值
val_SDS value的长度+9,jemalloc分配>=该值的2n的值
key的个数 所有的key的个数
bucket个数 大于key的个数的2n次方,例如key个数是2000,那么bucket=2048
指针大小 8byte

例如执行set user.name=admin,需要内存计算如下所示:
t o t a l = 32 + 16 + ( 9 + 9 = 18 − > 32 ) + ( 5 + 9 = 14 − > 16 ) = 96 字 节 total=32+16+(9+9=18->32)+(5+9=14->16)=96字节 total=32+16+(9+9=18>32)+(5+9=14>16)=96

Hash

Hash结构在使用HTable数据类型时,value并不是指向一个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字符串;

公式:
总 内 存 消 耗 = [ ( r e d i s O b j e c t 大 小 × 2 + f i e l d S D S 大 小 + v a l S D S 大 小 + d i c t E n t r y 大 小 ) × f i e l d 个 数 + f i e l d b u c k e t 个 数 × 指 针 大 小 + d i c t 大 小 + r e d i s O b j e c t 大 小 + k e y S D S 大 小 + d i c t E n t r y 大 小 ] × k e y 个 数 + k e y b u c k e t 个 数 × 指 针 大 小 总内存消耗 = [(redisObject大小 ×2 +field SDS大小 +val SDS大小 + dictEntry大小)× field个数 +field bucket个数× 指针大小 + dict大小 + redisObject大小 +key SDS大小 + dictEntry大小 ] × key个数 +key bucket个数×指针大小 =[redisObject×2+fieldSDS+valSDS+dictEntry×field+fieldbucket×+dict+redisObject+keySDS+dictEntry]×key+keybucket×

SortedSet

一个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字节,用作成员字符串;

公式:
总 内 存 消 耗 = [ ( v a l S D S 大 小 + r e d i s O b j e c t 大 小 + z s k i p l i s t N o d e 大 小 + d i c t E n t r y 大 小 ) × v a l u e 个 数 + v a l u e B u c k e t 个 数 × 指 针 大 小 + 32 层 z s k i p l i s t N o d e 大 小 + z s k i p l i s t 大 小 + d i c t 大 小 + z s e t 大 小 + r e d i s O b j e c t 大 小 + k e y S D S 大 小 + d i c t E n t r y 大 小 ] × k e y 个 数 + k e y B u c k e t 个 数 × 指 针 大 小 总内存消耗 = [(valSDS大小 + redisObject大小 + zskiplistNode大小 + dictEntry大小)×value个数 +valueBucket个数 ×指针大小 + 32层zskiplistNode大小 + zskiplist大小 + dict大小 + zset大小 + redisObject大小 +keySDS大小 + dictEntry大小 ] ×key个数 +keyBucket个数 × 指针大小 =[(valSDS+redisObject+zskiplistNode+dictEntry×value+valueBucket×+32zskiplistNode+zskiplist+dict+zset+redisObject+keySDS+dictEntry]×key+keyBucket×

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字节,用作值对象指向的字符串;

公式:
总 内 存 消 耗 = [ ( v a l S D S 大 小 + r e d i s O b j e c t 大 小 + l i s t N o d e 大 小 ) × v a l u e 个 数 + l i s t 大 小 + r e d i s O b j e c t 大 小 + k e y S D S 大 小 + d i c t E n t r y 大 小 ] × k e y 个 数 + k e y b u c k e t 个 数 × 指 针 大 小 总内存消耗 = [(val_SDS大小 + redisObject大小 + listNode大小)× value个数 + list大小 + redisObject大小+key_SDS大小 + dictEntry大小 ] × key个数 +key_bucket个数 × 指针大小 =[(valSDS+redisObject+listNode×value+list+redisObject+keySDS+dictEntry]×key+keybucket×

其他内存命令

memory usage

用户查看一个key的内存占用情况

memory usage keyname

memory doctor

在redis-cli中运行memory doctor命令,如果内存使用有明显不合里的情况,会给出不合理的状态,同时给出处理的建议。

> memory doctor

例如:
运行良好:

Hi Sam, I can't find any memory issue in your instance. I can only account for what occurs on this base.

碎片率过高:

 Peak memory: In the past this instance used more than 150% the memory that is currently using. The allocator is normally not able to release memory after a peak, so you can expect to see a big fragmentation ratio, however this is actually harmless and is only due to the memory peak, and if the Redis instance Resident Set Size (RSS) is currently bigger than expected, the memory will be used as soon as you fill the Redis instance with more data. If the memory peak was only occasional and you want to try to reclaim memory, please try the MEMORY PURGE command, otherwise the only other option is to shutdown and restart the instance.

memory doctor会在以下情况做出提醒:

  • 当前占用内存total.allocated小于5M,doctor认为内存使用量过小,不做进一步诊断;
  • peak分配内存大于当前total_allocated的1.5倍(peak.allocated/total.allocated>1.5),可能说明RSS远大于used_memory;
  • 内存碎片率fragmentation大于1.4;
  • 每个Normal Client平均使用内存大于200KB;
  • 每个Slave Client平均使用内存大于10MB。

memory purge

> memory purge

用于内存回收,在碎片率过高的情况下,可以使用。


【参考内容】

  • https://blog.csdn.net/zhengpeitao/article/details/76573053?utm_source=copy
  • https://www.cnblogs.com/kismetv/p/8654978.html

你可能感兴趣的:(数据库)