当前位置: 首页 > news >正文

Redis中的sdshdr的len和alloc那块的知识点详解

文章目录

      • 核心比喻:一个可以伸缩的水瓶
      • 场景一:创建一个新字符串
      • 场景二:追加字符串(触发“空间预分配”)
      • 场景三:再次追加字符串(利用空闲空间)
      • 场景四:缩短字符串(惰性空间释放)
      • 总结

我们用一个非常形象的比喻和几个实际的例子,来彻底搞懂 lenalloc 这两个概念。

核心比喻:一个可以伸缩的水瓶

想象一下,Redis 的一个字符串(SDS)就是一个“智能水瓶”,而 sdshdr 就是瓶身上的刻度标签。

  • len (length): 代表瓶子里当前装了多少水。这是字符串的实际、有效长度。
  • alloc (allocation): 代表这个瓶子的最大容量。这是系统为这个字符串总共分配了多大的内存空间(不含头部和末尾的 \0)。
  • 空闲空间 (free space): alloc - len,就代表瓶子里还能装多少水,也就是预留的空闲内存。

关键点:瓶子的容量 (alloc) 永远大于或等于瓶子里的水量 (len)。

这个设计的核心目的,就是避免频繁地“换瓶子”。因为“换瓶子”(内存重分配)是一个非常耗时的操作。如果每次只加一滴水,你都要去找一个大小刚刚好的新瓶子,效率会非常低下。


场景一:创建一个新字符串

当你执行 SET mykey "hello" 时,Redis 创建了一个 SDS 字符串。

  • 字符串 “hello” 的长度是 5。
  • Redis 内部会创建一个 sdshdr 和一块内存来存放它。假设它选择 sdshdr8 类型(头部占3字节)。
  • 为了效率,它可能不会只分配刚刚好的5字节,但我们为了简化,先假设它就是这么做的。

内存状态:

  • len = 5 (瓶里有5个单位的水)
  • alloc = 5 (瓶子总容量是5)
  • 内存布局大致如下:
    [sdshdr8: len=5, alloc=5] ['h','e','l','l','o','\0']
    
    此时,空闲空间 alloc - len 为 0。瓶子是满的。

场景二:追加字符串(触发“空间预分配”)

现在,我们来追加内容,执行 APPEND mykey " world"

  1. 检查空间:

    • 要追加的字符串 " world" 长度为 6。
    • API 首先检查空闲空间:alloc(5) - len(5) = 0
    • 0 < 6,空间不够!需要“换个大瓶子”(重新分配内存)。
  2. 执行空间预分配(The Magic):

    • 新的字符串总长度将是 len(5) + 6 = 11
    • Redis 不会只分配 11 字节的新空间,它会想:“你既然开始追加了,很可能等下还会再追加。”
    • 于是它启动空间预分配策略:新分配的容量 alloc 会是 新长度的两倍 (在字符串总长小于1MB时)。
    • 新的 alloc = 11 * 2 = 22
    • Redis 会申请一块能容纳 22 个字符的新内存空间,并把旧内容 “hello” 和新内容 " world" 拷贝进去。

内存状态更新:

  • len = 11 (现在瓶里有11个单位的水)
  • alloc = 22 (但瓶子的总容量是22!)
  • 内存布局大致如下:
    [sdshdr16: len=11, alloc=22] ['h','e','l','l','o',' ','w','o','r','l','d','\0', ...11 bytes free... ]<---- len = 11 ----> <---- free = 11 ----><----------------- alloc = 22 ----------------->
    
    现在,alloc(22) - len(11) = 11,我们有 11 个字节的空闲空间

场景三:再次追加字符串(利用空闲空间)

我们继续追加,执行 APPEND mykey "!!!"

  1. 检查空间:

    • 要追加的 “!!!” 长度为 3。
    • API 检查空闲空间:alloc(22) - len(11) = 11
    • 11 >= 3,空间足够!不需要重新分配内存!
  2. 原地修改:

    • 直接在 buf 的末尾把 “!!!” 写进去。
    • 只更新头部的 len 字段。

内存状态更新:

  • len = 11 + 3 = 14 (水变多了)
  • alloc = 22 (瓶子还是那个瓶子,容量没变)
  • 内存布局大致如下:
    [sdshdr16: len=14, alloc=22] ['h','e','l','l','o',' ','w','o','r','l','d','!','!','!','\0', ...8 bytes free... ]<------ len = 14 ------> <---- free = 8 ----><------------------- alloc = 22 ------------------->
    
    这个操作非常快,因为它避免了最耗时的内存分配和数据拷贝。

场景四:缩短字符串(惰性空间释放)

现在,我们把这个 key 的值改成一个很短的字符串,执行 SET mykey "Hi"

  • Redis 会直接用 “Hi” 覆盖掉 buf 开头的内容。
  • 然后,它只会更新 len 字段。

内存状态更新:

  • len = 2 (水变得很少)
  • alloc = 22 (但瓶子还是那个大瓶子!)
  • 内存布局大致如下:
    [sdshdr16: len=2, alloc=22] ['H','i','\0','l','o',' ','w','o','r','l','d', ...garbage data..., ...free space... ]<len=2> <----------------- free = 20 -----------------><------------------- alloc = 22 ------------------->
    
  • 这被称为惰性空间释放。Redis 不会立即把多余的 20 字节空间还给操作系统。它会保留这些空间,因为你可能马上又会执行 APPEND 操作,这样就又可以利用上这些预留空间了。

总结

lenalloc 的设计哲学,是典型的用空间换时间的思想。

  • len 提供了 O(1)O(1)O(1) 时间复杂度的长度获取能力,并且是二进制安全的基础。
  • alloc 配合空间预分配惰性释放策略,极大地减少了内存重分配的次数,这是 Redis 字符串追加(APPEND)操作如此高效的关键所在。它将多次可能发生的、耗时的内存操作,均摊到了一次操作中。
http://www.lryc.cn/news/611381.html

相关文章:

  • 前端记录项目中用到的js
  • python可视化--Seaborn图形绘制方法和技巧,Bokeh图形绘制方法和技巧
  • 最新基于Python科研数据可视化实践技术
  • 磁悬浮转子振动控制:主动电磁力如何成为高速旋转的“振动克星”
  • css动态样式
  • 【Git学习】入门与基础
  • Cisco 3750X交换机更新到IOS 15.2后无法启动 提示:Boot process failed...
  • Laravel The requested URL /hellowzy was not found on this server. 404 问题的解决
  • 嵌入式 - 数据结构:循环链表和内核链表
  • ES 模块动态导入
  • Python深度学习:从入门到进阶
  • 《四种姿势用Java玩转AI大模型:从原生HTTP到LangChain4j》
  • 如何在nuxt项目中进行meta信息注入
  • 【RabbitMQ】高级特性—消息确认详解
  • 探索设计模式的宝库:Java-Design-Patterns
  • Android UI 组件系列(十一):RecyclerView 多类型布局与数据刷新实战
  • MongoDB学习专题(二)核心操作
  • 《前端安全攻防》
  • java线程同步工具:`synchronized`、`ReentrantLock`与其他并发工具的对比与应用
  • Kafka自动消费消息软件(自动化测试Kafka)
  • python的高校班级管理系统
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-登录实现
  • SpringCloud学习------Gateway详解
  • 将普通用户添加到 Docker 用户组
  • 虚幻GAS底层原理解剖二 (GE)
  • 如何用分布式架构视角理解宇宙稳定性?从精细调参到微服务的类比思考
  • 天津大学2024-2025 预推免 机试题目(第二批)
  • 关于内核启动的optee: probe of firmware: optee failed with error -22 固件拉起失败的问题
  • 《软件测试与质量控制》实验报告四 性能测试
  • HPE磁盘阵列管理01——MSA和SMU