数据库专题 —— Redis (2)

750 阅读4分钟
原文链接: zhuanlan.zhihu.com

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 属性将这些字节的数量记录起来,
并等待将来使用。