大家好,这里是编程Cookbook。本文详细介绍Redis的内存预分配策略,在使用各数据结构类型时,内存是如何变化的,以及触发底层数据结构变化的条件。

目录
什么是内存预分配? 各数据结构的实现 String(字符串) Hash(哈希) List(列表) Set(集合) Sorted Set(有序集合) 内存预分配的优缺点 优点 缺点 总结
Redis 的内存预分配策略是一种优化手段,用来减少频繁的内存分配和释放操作对性能的影响。通过预先分配足够的内存,Redis 可以提高操作效率(在很多编程语言,如Go的切片,中都有实现),尤其是在高并发场景下。
什么是内存预分配?
定义: Redis 在某些数据结构(如字符串、列表、哈希等)存储数据时,不是每次都按照精确的内存需求分配,而是会额外预留一部分内存空间。 目的: 减少频繁的内存分配系统调用。 提高数据结构扩展时的性能。 降低内存碎片化的风险。
各数据结构的实现
Redis 的多个数据结构会使用内存预分配策略,包括:
String(字符串)
背景:Redis 的字符串类型基于 SDS 实现,支持动态扩容。 触发条件: 当字符串长度超过当前分配的内存容量。 扩容策略: 短字符串(小于 1MB): 当 SDS 扩容时,新内存大小 = 当前长度的两倍。 例如:原字符串长度为 10 字节,扩容后长度为 20 字节。 长字符串(大于等于 1MB): 每次扩容固定增加 1MB。 例如:原字符串长度为 2MB,扩容后长度为 3MB。 好处: 对于短字符串,快速满足增长需求。 对于长字符串,减少频繁的大量扩容。 释放策略: 当字符串缩短时,预分配的空间不会立刻释放,但可以复用。
Hash(哈希)
背景:Redis 的哈希类型使用 ziplist 或 hashtable 存储。 触发条件: ziplist:键值对数量或单个键值长度超过限制。 hashtable:负载因子(填充率)超过阈值。 扩容策略:
ziplist: ziplist是一个固定大小的数据结构。它并不像hashtable那样支持动态扩容,而是使用固定的内存块来存储数据。它依赖于紧凑存储,如果遇到空间不足,它会通过切换为hashtable来处理大量数据的需求。 切换条件:Redis 在以下情况下,自动将 Hash 的底层存储从ziplist切换为hashtable: 字段数量超出阈值:默认 hash-max-ziplist-entries=512
,即当字段数量超过 512 时,切换为哈希表。单个字段或值的大小超出阈值:默认 hash-max-ziplist-value=64
,即当字段或值的大小超过 64 字节时,切换为哈希表。hashtable: 负载因子(填充率)超过阈值(默认是1.0),会按照下面进行扩容 按两倍扩展大小。 使用渐进式 rehash,分批次迁移数据,降低性能抖动。 当哈希表中的负载因子降到 小于 0.1 时(表空间使用率过低),Redis 会触发缩容: 将哈希表容量调整为当前元素数量的两倍(创建一个新的更小的哈希表。将旧表中的数据逐步迁移到新表),以保证负载因子接近 0.5。 使用渐进式 rehash,分批次迁移数据,降低性能抖动。 好处: 避免小型哈希频繁扩容问题。 哈希表扩展为两倍,保证插入操作性能。
List(列表)
背景:Redis 的列表类型在底层使用 quicklist(快速列表)存储,每个节点是一个 ziplist(压缩列表)。
quicklist 是一个双向链表,每个节点存储一个 ziplist。 ziplist 是一个连续内存区域,存储多个列表元素。 分层机制:
quicklist提供灵活的节点管理能力。 ziplist提供紧凑的内存存储,节省空间。 触发条件:
ziplist:
当新增元素导致ziplist的容量不足,超出其当前内存空间限制时触发扩容。
当ziplist达到容量限制,或者 quicklist 的设计策略要求分离数据时,触发 quicklist 新建节点操作。
quicklist:
如果 ziplist 达到配置的最大容量限制(由
list-max-ziplist-size
控制),则 quicklist 会拆分出一个新的节点。扩容策略:
ziplist:
扩容是成比例的,通常为当前容量的两倍,以减少未来的扩容频率。
ziplist 的内存空间是连续的,因此扩容需要分配一段新的连续内存,并将数据迁移到新区域。
quicklist:
在新建节点时,会根据需求适当预留更多容量,用于存储未来插入的数据。
这种设计避免了频繁创建新节点或调整链表结构的开销。
好处:
快速插入和追加,减少扩容次数。 降低链表操作带来的内存分配开销。
Set(集合)
背景:Redis 的集合类型使用 intset(整数集合)或 hashtable 存储。
触发条件:
intset扩容触发条件:
插入元素类型超出当前范围:
intset使用紧凑的存储方式,只支持
int16
、int32
或int64
类型整数。如从int16
扩展为int32
。插入非整数值或集合大小超过阈值(默认 512 个):
默认情况下,当集合中的元素数量或类型复杂度超出 intset 的范围时,Redis 会将集合从intset转换为hashtable。
hashtable扩容触发条件:负载因子(填充率)超过阈值。
扩容策略:
intset:当插入的整数类型需要更大的存储空间时,intset 会升级存储类型,例如从 int16 升级到 int32,或者从 int32 升级到 int64。 hashtable:按两倍容量扩展。(同上面的hash中的扩容策略) 好处:
减少扩容性能损耗。 在集合规模增长时动态调整存储结构。
Sorted Set(有序集合)
背景:Redis 的有序集合使用 ziplist 或 zskiplist(跳跃表)存储。
触发条件:
zskiplist: ziplist 的扩容触发条件: 新增元素导致内存空间不足:当有序集合使用 ziplist 存储且现有内存空间不足以容纳新元素时,会触发动态扩容。 ziplist 转换为 zskiplist 的触发条件:
集合元素数量超出阈值:默认情况下,如果有序集合中的元素数量超过128 个(由配置项
zset-max-ziplist-entries
控制),Redis 会将底层存储从ziplist切换为zskiplist。单个元素长度超出阈值:默认情况下,如果有序集合中的任意元素长度超过64 字节(由配置项
zset-max-ziplist-value
控制),也会触发结构转换。ziplist: 增加元素需要优化查询效率:跳跃表通过增加索引层数来优化查询性能。当插入大量元素或元素分布范围广泛时,可能触发增加索引层数以维持平均 O(log N) 的查询效率。 扩容策略:

好处:
优化小集合存储空间。 提升大规模有序集合的插入性能。
内存预分配的优缺点
优点
减少系统调用: 批量分配内存,降低内存分配和释放的频率。 提高性能: 数据扩展时无需频繁重新分配内存。 避免碎片: 内存分配更加连续,减少碎片化。
缺点
可能导致内存浪费: 预分配的内存如果未使用,可能造成一定的浪费。 释放延迟: 预分配的内存不会及时回收,仅在更大范围的操作中释放。
总结
Redis 的内存预分配策略结合动态扩容机制,有效提高了性能并降低内存分配开销。预分配策略的设计在多种数据结构中得到了应用,同时通过合理的参数调整,可以进一步优化 Redis 的内存利用效率。
热门文章回顾
欢迎订阅我们的Cookbook知识合集,目前《MySQL数据库》合集已基本更新完成,欢迎点击下方图片进入合集目录。





