Redis学习目录总结02-Redis内存划分

176 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

Redis在内存中存储的内容主要是数据(键值对)除了数据以外,Redis的其他部分也会占用内存。

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

1、数据内存

数据内存会统计在used_memory中。

  • 对外提供:值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。
  • 在Redis内部,每种类型可能有2种或更多的内部编码实现;
  • Redis在存储对象时,并不是直接将数据扔进内存,先会对 对象进行各种包装:例如RedisObject、String(SDS-Simple Dynamic String)等

RedisObject

  • 所有的Redis对象都有下面的这个头结构
  • 不同的对象具有不同的类型 type (4bit)
  • 同一个类型的 type 会有不同的存储形式 encoding (4bit)
  • 用了 24 个 bit 来记录 记录对象的 LRU 信息。
  • 每个对象都有个引用计数,当引用计数为零时,对象就会被销毁,内存被回收。
  • ptr 指针将指向对象内容(body)的具体存储位置。这样一个 RedisObject 对象头结构 需要占据 16 字节的存储空间。
struct RedisObject { 
    int4 type;       // 4bit
    int4 encoding;   // 4bit
    int24 lru;       // 24bit
    int32 refcount;  // 4bytes
    void *ptr;       // 8bytes, 64-bit system
} 

String (SDS)

  • C 语言里面的字符串标准形式是以 NULL (即 0x\0)作为结束符,但是在 Redis 里面字符串不是这么表示的。因为要获取以 NULL 结尾的字符串的长度使用的是 strlen 标准库函数,这个函数的算法时间复杂度是 O(n),它需要对字节数组进行遍历。
  • Redis的字符串是动态字符串,是可以修改的字符串,称为“SDS”,也就是 Simple Dynamic String的缩写。它的结构是一个带长度信息的字节数组。
  • 内部结构的实现类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
  • 当前字符串分配的实际空间 capacity 一般要高于实际字符串长度 len,content 里面存储了真正的字符串内容当字符串长度小于 1MB 时,扩容都是加倍现有的空间。如果字符串长度超过 1MB,扩容时一次只会多扩 1MB 的空间。需要注意的是字符串最大长度为 512MB。

2、Redis进程运行占用的内存

  • Redis主进程本身运行占用内存,如代码、常量池等等,这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。
  • 这部分内存不是由jemalloc分配,因此不会统计在used_memory中。
  • Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。

3、Redis缓冲内存

缓冲内存包括客户端缓冲区(输入缓冲区、输出缓冲区)、复制积压缓冲区、AOF缓冲区等;

  • 客户端缓冲 存储客户端服务连接的 输入命令和输出结果缓冲内存;
  • 复制积压缓冲用于部分复制功能;
  • AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。 这部分内存由jemalloc分配,因此会统计在used_memory中。

4、Redis内存碎片

  • 内存碎片是Redis在分配、回收物理内存过程中产生的。举个简单的例子,做飞机每次都不可能满员,空余的位置就是剩余的内存碎片空间。
  • 如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。
  • 内存碎片不会统计在used_memory中
  • 内存分配器的分配策略决定操作系统无法做到精准分配,内存分配器必须分配一块固定大小的连续内存空间。jemalloc在控制内存碎片方面做的不错,后续会介绍。