暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis数据库——内存预分配

编程Cookbook 2025-03-24
116

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


目录

  • 什么是内存预分配?
  • 各数据结构的实现
    • String(字符串)
    • Hash(哈希)
    • List(列表)
    • Set(集合)
    • Sorted Set(有序集合)
  • 内存预分配的优缺点
    • 优点
    • 缺点
  • 总结

Redis 的内存预分配策略是一种优化手段,用来减少频繁的内存分配和释放操作对性能的影响。通过预先分配足够的内存,Redis 可以提高操作效率(在很多编程语言,如Go的切片,中都有实现),尤其是在高并发场景下。


什么是内存预分配?

  • 定义: Redis 在某些数据结构(如字符串、列表、哈希等)存储数据时,不是每次都按照精确的内存需求分配,而是会额外预留一部分内存空间
  • 目的
    • 减少频繁的内存分配系统调用。
    • 提高数据结构扩展时的性能。
    • 降低内存碎片化的风险。

各数据结构的实现

Redis 的多个数据结构会使用内存预分配策略,包括:

String(字符串)

  • 背景:Redis 的字符串类型基于 SDS 实现,支持动态扩容。
  • 触发条件
    • 当字符串长度超过当前分配的内存容量。
  • 扩容策略
        1. 短字符串(小于 1MB)
          当 SDS 扩容时,新内存大小 = 当前长度的两倍
          例如:原字符串长度为 10 字节,扩容后长度为 20 字节。
        2. 长字符串(大于等于 1MB)
          每次扩容固定增加 1MB
          例如:原字符串长度为 2MB,扩容后长度为 3MB。
      • 好处
        • 对于短字符串,快速满足增长需求。
        • 对于长字符串,减少频繁的大量扩容。
      • 释放策略
        • 当字符串缩短时,预分配的空间不会立刻释放,但可以复用。

      Hash(哈希)

      • 背景:Redis 的哈希类型使用 ziplist  hashtable 存储。
      • 触发条件
        1. ziplist:键值对数量或单个键值长度超过限制。
        2. hashtable:负载因子(填充率)超过阈值。
      • 扩容策略
        • ziplist
          • ziplist是一个固定大小的数据结构。它并不像hashtable那样支持动态扩容,而是使用固定的内存块来存储数据。它依赖于紧凑存储,如果遇到空间不足,它会通过切换为hashtable来处理大量数据的需求。
          • 切换条件:Redis 在以下情况下,自动将 Hash 的底层存储从ziplist切换为hashtable
            1. 字段数量超出阈值:默认hash-max-ziplist-entries=512
              ,即当字段数量超过 512 时,切换为哈希表。
            1. 单个字段或值的大小超出阈值:默认hash-max-ziplist-value=64
              ,即当字段或值的大小超过 64 字节时,切换为哈希表。
        • hashtable
          • 负载因子(填充率)超过阈值(默认是1.0),会按照下面进行扩容
            • 按两倍扩展大小
            • 使用渐进式 rehash,分批次迁移数据,降低性能抖动。
          • 当哈希表中的负载因子降到 小于 0.1 时(表空间使用率过低),Redis 会触发缩容
            • 将哈希表容量调整为当前元素数量的两倍(创建一个新的更小的哈希表。将旧表中的数据逐步迁移到新表),以保证负载因子接近 0.5。
            • 使用渐进式 rehash,分批次迁移数据,降低性能抖动。
      • 好处
        • 避免小型哈希频繁扩容问题。
        • 哈希表扩展为两倍,保证插入操作性能。

      List(列表)

      • 背景:Redis 的列表类型在底层使用 quicklist(快速列表)存储,每个节点是一个 ziplist(压缩列表)。

        1. quicklist 是一个双向链表,每个节点存储一个 ziplist
        2. ziplist 是一个连续内存区域,存储多个列表元素。
        3. 分层机制
          • quicklist提供灵活的节点管理能力。
          • ziplist提供紧凑的内存存储,节省空间。
      • 触发条件

        1. ziplist

          1. 当新增元素导致ziplist的容量不足,超出其当前内存空间限制时触发扩容。

          2. ziplist达到容量限制,或者 quicklist 的设计策略要求分离数据时,触发 quicklist 新建节点操作。

        2. quicklist

          1. 如果 ziplist 达到配置的最大容量限制(由list-max-ziplist-size
            控制),则 quicklist 会拆分出一个新的节点。

      • 扩容策略

            1. ziplist

              1. 扩容是成比例的,通常为当前容量的两倍,以减少未来的扩容频率。

              2. ziplist 的内存空间是连续的,因此扩容需要分配一段新的连续内存,并将数据迁移到新区域。

            2. quicklist

              1. 新建节点时,会根据需求适当预留更多容量,用于存储未来插入的数据。

              2. 这种设计避免了频繁创建新节点或调整链表结构的开销。

          • 好处

            • 快速插入和追加,减少扩容次数。
            • 降低链表操作带来的内存分配开销。

          Set(集合)

          • 背景:Redis 的集合类型使用 intset(整数集合)或 hashtable 存储。

          • 触发条件:

              1. intset扩容触发条件:

                1. 插入元素类型超出当前范围

                  • intset使用紧凑的存储方式,只支持int16
                    int32
                    int64
                    类型整数。如从int16
                    扩展为int32

                2. 插入非整数值或集合大小超过阈值(默认 512 个)

                  • 默认情况下,当集合中的元素数量或类型复杂度超出 intset 的范围时,Redis 会将集合从intset转换为hashtable

              2. hashtable扩容触发条件:负载因子(填充率)超过阈值。

            1. 扩容策略

              1. intset:当插入的整数类型需要更大的存储空间时,intset 会升级存储类型,例如从 int16 升级到 int32,或者从 int32 升级到 int64。
              2. hashtable:按两倍容量扩展。(同上面的hash中的扩容策略
            2. 好处

              • 减少扩容性能损耗。
              • 在集合规模增长时动态调整存储结构。

            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) 的查询效率。
                    • 扩容策略:

                    • 好处

                      • 优化小集合存储空间。
                      • 提升大规模有序集合的插入性能。

                    内存预分配的优缺点

                    优点

                    1. 减少系统调用
                      • 批量分配内存,降低内存分配和释放的频率。
                    2. 提高性能
                      • 数据扩展时无需频繁重新分配内存。
                    3. 避免碎片
                      • 内存分配更加连续,减少碎片化。

                    缺点

                    1. 可能导致内存浪费
                      • 预分配的内存如果未使用,可能造成一定的浪费。
                    2. 释放延迟
                      • 预分配的内存不会及时回收,仅在更大范围的操作中释放。

                    总结

                    Redis 的内存预分配策略结合动态扩容机制,有效提高了性能并降低内存分配开销。预分配策略的设计在多种数据结构中得到了应用,同时通过合理的参数调整,可以进一步优化 Redis 的内存利用效率。

                    热门文章回顾

                    欢迎订阅我们的Cookbook知识合集,目前《MySQL数据库》合集已基本更新完成,欢迎点击下方图片进入合集目录




                    文章转载自编程Cookbook,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                    评论