Redis 的内存模型
1、Redis 内存统计
# info 命令可以显示 Redis 服务器的基本信息,包括服务器基本信息、CPU、内存、持久化、客户端连接信息等
> info memory
used_memory:1537568 # Redis 分配器分配的内存总量,包括使用的虚拟内存
used_memory_human:1.47M
used_memory_rss:704512 # Redis 进程占据操作系统的内存,包括内存碎片
used_memory_rss_human:688.00K
used_memory_peak:1537568 # Redis 内存使用的峰值
used_memory_peak_human:1.47M
used_memory_peak_perc:100.01% # 使用内存达到峰值内存的占比
used_memory_overhead:1031150 # Redis 维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog
used_memory_startup:980736 # Redis 服务器启动时消耗的内存
used_memory_dataset:506418 # 数据占用的内存大小,used_memory - used_memory_overhead
used_memory_dataset_perc:90.95% # 数据占用的内存的占比,(used_memory_dataset/(used_memory-used_memory_startup))*100%
total_system_memory:17179869184 # 系统内存
total_system_memory_human:16.00G
used_memory_lua:37888 # Lua 脚本存储占用的内存
used_memory_lua_human:37.00K
maxmemory:0 # Redis 实例的最大内存配置
maxmemory_human:0B
maxmemory_policy:noeviction # 当达到 maxmemory 时的淘汰策略
mem_fragmentation_ratio:0.46 # 内存碎片化比率,值小于1,说明使用了虚拟内存
mem_allocator:libc # Redis 使用的内存分配器,可以是 libc / jemalloc / tcmalloc
active_defrag_running:0 # 是否有内存碎片整理任务运行,0 表示没有
lazyfree_pending_objects:0 # 是否存在延迟释放的挂起对象
2、Redis 数据存储
dictEntry:
Redis 中的每个键值对都会有一个 dictEntry,存储指向 Key 和 Value 的指针,next 指向下一个 dictEntry,用于解决冲突(链表法)。
RedisObject:
Redis 中的 Value 都通过 RedisObject 来存储,type 指明了 Value 对象的类型,ptr 字段指向对象所在的地址。除此之外,还会包含对象编码的信息等。
# 获取对象类型
> type key-name
# 获取对象编码方式
> object encoding key-name
# lru 存储对象最后一次被命令程序访问的时间
> object idletime key-name # 空转时间
# 对象被引用的次数
> object refcount key-name
SDS
Redis 将 SDS( simple dynamic string )用于默认字符串的表示。
# 1. SDS 的定义
struct sdshdr {
// 数组中已使用的字节的数量 = 字符串的长度
int len;
// 数组中未使用的字节的数量
int free;
// 字节数组
char buf[];
};
图示:
--------
| sdshdr |
--------
| free |
| 2 |
--------
| len |
| 5 |
-------- --------------------------------------------
| buf | ---> | 'R' | 'e' | 'd' | 'i' | 's' | '\0' | | |
-------- --------------------------------------------
SDS 遵循 C 语言字符串以空字符串结尾的惯例,保存空字符串的 1 字节空间不计算在 SDS 的 len 属性
里面,并且为空字符分配额外的 1 字节空间,以及添加空字符到字符串末尾等操作,都是由 SDS 自动完成。
遵循空字符结尾的好处是:SDS 可以直接重用一部分 C 语言字符串函数库里面的函数。
# 2. SDS 与 C 语言字符串的区别
----------------------------------------------------------------------
| C字符串 | SDS |
----------------------------------------------------------------------
| 获取字符串长度的时间复杂度为 O(N) | 获取字符串长度的时间复杂度为 O(1) |
| 可能会造成缓存区溢出 | 不会造成缓存区溢出 |
| 修改字符串 n 次必然执行 n 次内存重分配 | 修改字符串 n 次最多执行 n 次内存重分配|
| 只能保存文本数据 | 可以保存文本或者二进制数据 |
| 可以使用所有字符串函数库里面的函数 | 部分使用所有字符串函数库里面的函数 |
----------------------------------------------------------------------
# 3. SDS 减少内存重分配的优化策略
## 3.1 空间预分配
空间预分配用于优化 SDS 的字符串增长操作。
额外分配的未使用空间数量的公式:
(1) 如果对 SDS 进行修改之后,SDS 的长度小于 1MB,那么程序分配和 len 属性同样大小的未使用空间,
这时 SDS 的 len 属性的值将和 free 属性的值相同
(2) 如果对 SDS 进行修改之后,SDS 的长度将大于等于 1MB,那么程序会分配 1MB 的未使用空间
## 3.2 惰性空间释放
惰性空间释放用于优化 SDS 的字符串缩短操作。
当缩短字符串时,程序并不会立即回收缩短后多出来的字节,而是使用 free 属性将这些字节的数量记录起来,
并等待将来使用。