Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • ⛸️ Redis Implementation
    • Part 1 - 数据结构与对象

      • Chapter 2 - 简单动态字符串
      • Chapter 3 - 链表
      • Chapter 4 - 字典
      • Chapter 5 - 跳跃表
      • Chapter 6 - 整数集合
      • Chapter 7 - 压缩列表
      • Chapter 8 - 对象
    • Part 2 - 单机数据库的实现

      • Chapter 9 - 数据库
      • Chapter 10 - RDB 持久化
      • Chapter 11 - AOF 持久化
      • Chapter 12 - 事件
      • Chapter 13 - 客户端
      • Chapter 14 - 服务器
    • Part 3 - 多机数据库的实现

      • Chapter 15 - 复制
      • Chapter 16 - Sentinel
      • Chapter 17 - 集群
    • Part 4 - 独立功能的实现

      • Chapter 18 - 发布与订阅
      • Chapter 19 - 事务
      • Chapter 21 - 排序
      • Chapter 22 - 二进制位数组
      • Chapter 23 - 慢查询日志

Chapter 2 - 简单动态字符串

Created by : Mr Dk.

2020 / 06 / 01 14:12

Nanjing, Jiangsu, China


Redis 中自行构建了一种字符串表示,称为 简单动态字符串 (Simple Dynamic String, SDS),而普通的 C 字符串只会被用于一些字面常量。

Definition

SDS 的定义如下。对于这种设计,与传统的 C 字符串相比,有什么优势?

struct sdshdr {
    int len;
    int free;
    char buf[];
}
  • len 记录了字符串目前的长度 (不包括 \0)
  • free 记录了缓冲区中剩余空闲空间的长度
  • buf 指向缓冲区首地址

String Length

在普通的 C 字符串中,并不维护字符串的长度。如果要获得一个字符串的长度,strlen() 需要对整个字符串进行遍历,时间复杂度为 O(N)。Redis 的 SDS 中直接维护了字符串的长度,时间复杂度减小到 O(1) - 对于一个特别长的字符串反复执行 STRLEN 也不会对性能有影响。

Memory Security

C 字符串不记录自身长度带来的一个问题是容易造成缓冲区溢出。比如在进行 strcat(dest, src) 时,如果 dest 的缓冲区长度不够,将会覆盖 dest 缓冲区之后的内存内容。由于 SDS 记录了字符串长度,在字符串发生修改前,会对缓冲区长度进行检查。如果缓冲区长度不够,SDS 的空间将先被扩展,再进行后续操作。

Memory Allocation

扩展 SDS 的内存空间涉及到内存的重新分配。频繁的重分配肯定是相当耗时的。SDS 中实现了两种优化机制。

空间预分配

由于 SDS 中可以记录缓冲区中的空闲空间。为了减少内存重分配的次数,很容易想到的策略就是一次分配较多的空间,记录在 free 中,后续就不需要重复分配了。这种策略带来的问题就是会有一些空间被浪费。Redis 的实现在其中做了折衷:

  • 对 SDS 修改后,SDS 的长度 < 1MB,那么修改后 len 属性与 free 属性相同 (相当于空间扩展到实际内容的两倍长度)
  • 对 SDS 修改后,SDS 长度 >= 1MB,那么修改后将保证 free 为 1MB (分配 1MB 的额外空间)

通过这种空间预分配技术,可以在一定程度上减少内存重新分配的次数。

惰性内存释放

当 SDS 字符串缩短后,并不立刻回收内存,而是用 free 将空出的空间记录下来,以便下次使用。当然,也有对应的 API 可以真正释放这些未使用空间。

Binary Safety

SDS 从一定意义上来说已经不是字符串了,而是 字节数组。SDS API 会以二进制方式 (而不是字符串方式) 来处理缓冲区中的数据。比如,如果数据的中间有一个 \0,那么普通的 C 字符串 API 将会将字符串的一部分作为结果返回,从而带来错误。SDS API 不会出现这种问题。

同时,由于 SDS 中的 buf 也遵循 C 字符串的 \0 结尾惯例,因此也兼容部分 C 字符串函数。

Edit this page on GitHub
Next
Chapter 3 - 链表