Redis内存模型学习

学习redis内存模型,对于redis的内存使用会有更好的理解,有助于遇到与redis内存有关的问题的时候有更完善的分析解决思路。

查看Redis内存使用情况

在redis-cli 中使用info memory 查看redis内存使用情况。
Redis内存模型学习
used_memory 是Redis分配器分配的内存总量(单位是字节),包括使用的swap内存。

human后缀的都是显示出来人不用换算可以看明白的内存大小。

used_memory_rss 是Redis进程占据操作系统的内存(单位是字节),包含分配器分配的内存,redis进程需要的内存,内存碎片,但是不包含swap内存。

used_memory_peak是过去Redis内存使用的峰值,而不是当前使用内存的值。如果used_memory_peak和used_memory_rss的值大致上相等,而且二者明显超过了used_memory值,这说明额外的内存碎片正在产生。

used_memory_overhead 是Redis为了维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog。

used_memory_startup 是Redis服务器启动时消耗的内存。

used_memory_dataset 是used_memory - used_memory_overhead。

maxmemory_policy是当达到maxmemory时的淘汰策略。

mem_fragmentation_ratio:used_memory_rss / used_memory 表示redis的内存碎片率。
mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。如果mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。
一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);上面截图中的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据。
碎片过高的时候可以考虑重启 Redis 服务,在内存中对数据进行重排,减少内存碎片。
mem_allocator是内存分配器。

Redis的内存大致上可以分为四个部分,存放数据所需的内存,系统运行所需的内存,缓冲区内存,内存碎片。

Redis数据存储结构

redis 数据都存储在dictEntry的对象中
Redis内存模型学习
dictEntry 包含了一个key和对应的value。还有一个指向下一个元素的指针。

SDS

sds的结构包含int len,int free,char buf[]。
其中,buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。字符串结尾需要有\0。
通过SDS的结构可以看出,buf数组的长度=free+len+1(其中1表示字符串结尾的空字符);所以,一个SDS结构占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9 字节。

RedisObject

redis中的值都存储在RedisObject 中,它的结构是:
ypedef struct redisObject {
  unsigned type:4;
  unsigned encoding:4;
  unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
  int refcount;
  void *ptr;
} robj;

type字段表示对象的类型,占4个比特;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。

encoding表示对象的内部编码,占4个比特。每种类型都有至少两种内部编码,例如对于字符串,有int、embstr、raw三种编码。通过encoding属性,Redis可以根据不同的使用场景来为对象设置不同的编码,大大提高了Redis的灵活性和效率。一般情况下如果对象元素比较少的时候会采取压缩列表,减少存储空间,如果存储的数据元素比较多,会采取双端链表以便更方便的读取和修改。

lru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。通过对比lru时间与当前时间,可以计算某个对象的空转时间;object idletime命令可以显示该空转时间(单位是秒)。object idletime命令的一个特殊之处在于它不改变对象的lru值。

refcount记录的是该对象被引用的次数,用于对象的引用计数和内存回收。

ptr指针指向具体的数数据。

一个redisObject对象的大小为16字节:4bit+4bit+24bit+4Byte+8Byte=16Byte。

Redis的对象类型和内部编码

String

字符串是最基础的类型,字符串长度不能超过 512MB。
字符串类型的内部编码有 3 种:
int:8 个字节的长整型。字符串值是整型时,这个值使用 long 整型表示。
embstr:<=39 字节的字符串。embstr 与 raw 都使用 RedisObject 和 sds 保存数据。
区别在于:embstr 的使用只分配一次内存空间(因此 RedisObject 和 sds 是连续的),而 raw 需要分配两次内存空间(分别为 RedisObject 和 sds 分配空间)。
因此与 raw 相比,embstr 的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。
而 embstr 的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个 RedisObject 和 sds 都需要重新分配空间,因此 Redis 中的 embstr 实现为只读。
raw:大于 39 个字节的字符串。
embstr 和 raw 进行区分的长度是 39;是因为 RedisObject 的长度是 16 字节,sds 的长度是 9+ 字符串长度。因此当字符串长度是 39 时,embstr 的长度正好是 16+9+39=64,jemalloc 正好可以分配 64 字节的内存单元。
对于int如果长度超过long的范围的时候,会转换成raw.
对于embstr当需要修改的时候,也会转化成raw。

列表

列表(list)用来存储多个有序的字符串,每个字符串称为元素;一个列表可以存储 2^32-1 个元素。
Redis 中的列表支持两端插入和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。
支持压缩列表(ziplist)或双端链表(linkedlist)两种编码。
列表中元素数量小于 512 个且列表中所有字符串对象都不足 64 字节时会使用压缩列表,否则使用双端链表,且只能进行压缩列表到双端链表的单向转换。

哈希

哈希不仅是 Redis 对外提供的 5 种对象类型的一种,也是 Redis 作为 Key-Value 数据库所使用的数据结构。
哈希作为对象类型的时候内部编码可以是压缩列表(ziplist)和哈希表(hashtable)。
哈希中元素数量小于 512 个且哈希中所有键值对的键和值字符串长度都小于 64 字节时使用压缩列表,否则使用哈希表。并且只能从压缩列表到哈希表单向转换。

集合

集合(set)与列表类似,不同之处在于集合是无序的,并且不能保存重复的元素。一个集合中最多可以存储 2^32-1 个元素;除了支持常规的增删改查,Redis 还支持多个集合取交集、并集、差集。
集合的内部编码有整数集合和哈希表两种编码类型。
集合中元素数量小于 512 个,集合中所有元素都是整数值时,使用整数集合,否则使用哈希表,并且只能从整数集合到哈希表单向转换。

有序集合

有序集合与集合类似,不过是有序的,与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。
有序集合的内部编码有压缩列表(ziplist)或跳跃表(skiplist)两种编码类型。
跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
有序集合中元素数量小于 128 个;有序集合中所有成员长度都不足 64 字节时,使用压缩列表,否则使用跳跃表,并且只能从压缩列表到跳跃表单向转换。

Redis共享对象池

redis 为了节省内存空间,提供了整数值的字符串对象的共享。默认是0-9999,这个值可以通过调整参数 REDIS_SHARED_INTEGERS(4.0 中是 OBJ_SHARED_INTEGERS)的值进行改变。
但是在开启maxmemory和LRU淘汰策略时,对象池会关闭。因为lru字段也会被共享,此时淘汰策略就会不准确,LRU与共享对象池存在冲突,因此会关闭。

以上是我对Redis内存模型的学习,希望对你有所帮助。