计算Redis容量,并不只是仅仅计算key占多少字节,value占多少字节,因为Redis为了维护自身的数据结构,也会占用部分内存,本文章简单介绍每种数据类型(String
、Hash
、Set
、ZSet
、List
)占用内存量,供做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内存占用主要可以划分为如下几个部分:
dataset.bytes
包括key-value占用内存、dicEntry
占用内存、SDS
占用内存等。startup.allocated
,属于额外内存overhead.total
的一部分。fragmentation
,该值对于查看内存是否够用比较重要:该值一般>1,数值越大,说明内存碎片越多。如果<1,说明Redis占用了虚拟内存,而虚拟内存是基于磁盘的,速度会变慢,所以如果<1,就需要特别注意是否是内存不足了。
一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);上面截图中的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。
在了解了Redis内存模型之后,我们可以知道,Redis所占内存不仅仅时key-value,很包括其他内存占用。现在我们就来了解下Redis数据内存是如何占用的。
Redis数据内存除了包括key-value
,还包括dicEntry
、redisObject
、SDS
等。
公式:
总 内 存 消 耗 = ( 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结构在使用HTable数据类型时,value并不是指向一个SDS,而实又一个Dict结构。Dict结构保存了Hash对象的键值对。
一个hmset命令最终会产生以下几个消耗内存的结构:
公式:
总 内 存 消 耗 = [ ( 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个数×指针大小
一个zadd命令最终会产生以下几个消耗内存的结构:
公式:
总 内 存 消 耗 = [ ( 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个数×指针大小+32层zskiplistNode大小+zskiplist大小+dict大小+zset大小+redisObject大小+keySDS大小+dictEntry大小]×key个数+keyBucket个数×指针大小
一个rpush或者lpush命令最终会产生以下几个消耗内存的结构:
公式:
总 内 存 消 耗 = [ ( 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个数×指针大小
用户查看一个key的内存占用情况
memory usage keyname
在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.allocated
/total.allocated
>1.5),可能说明RSS远大于used_memory;fragmentation
大于1.4;> memory purge
用于内存回收,在碎片率过高的情况下,可以使用。
【参考内容】