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 属性将这些字节的数量记录起来,
并等待将来使用。
对于数据存储空间的操作
Redis 数据存储的空间即为键空间,对于键空间,我们可以进行很多操作:
常规的增 / 删 / 改 / 查
# 清空整个数据库
> flushdb
# 随机返回某个键
> randomkey
# 数据库中键的数量
> dbsize
# 查找所有符合给定模式 pattern 的 key
> keys *
当使用命令对 Redis 进行读写时,服务器还会执行一些额外的维护操作,包括:
- 在读取一个键之后(读操作和写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中次数或键空间不命中次数,这两个值可以在
INFO stats
命令的keyspace_hits
属性和keyspace_misses
属性中查看 - 在读取一个键之后,服务器会更新键的
LRU
(最后一次使用)时间 - 如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行余下的其他操作(第 4 篇文章详细说明)
- 如果有客户端使用
watch
命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为脏,从而让事务程序注意到这个键已经被修改过 - 服务器每次修改一个键之后,都会对脏键计数器的值增 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 # 是否存在延迟释放的挂起对象
redis 4.0 新增一个 MEMORY 命令,用于观察内存使用情况,并进行相应的内存管理操作
> MEMORY HELP
1) "MEMORY DOCTOR - Outputs memory problems report"
2) "MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key"
3) "MEMORY STATS - Show memory usage details"
4) "MEMORY PURGE - Ask the allocator to release memory"
5) "MEMORY MALLOC-STATS - Show allocator internal stats"
# MEMORY USAGE 估算给定键的内存
# MEMORY STATS 查看 Redis 当前的内存使用情况
# MEMORY PURGE 要求分配器释放更多的内存
# MEMORY MALLOC-STATS 展示分配器内部状态