Redis String 原理与源码分析

Redis String 原理与源码分析

Redis String 是 Redis 最基础的数据类型之一,采用键值对的形式存储数据。String 的值可以是字符串、整数或浮点数,并且是二进制安全的。本文将分析 Redis 7.0.5 中 String 的实现原理,重点讨论三种编码方式、SDS 的必要性及其优点,以及 SDS 的实现原理,包括扩容机制和内存管理。

三种编码方式

Redis String 有三种内部编码方式:int、embstr 和 raw。编码方式的选择取决于 String 的长度和类型。

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
  1. int 编码: 当 String 的值为整数且长度小于等于 21 个字符时,使用 int 编码。此时,值直接存储在 RedisObject 的 ptr 指针中,无需额外的内存分配。

  2. embstr 编码: 当 String 的值为字符串且长度小于等于 44 个字节时,使用 embstr 编码。embstr 编码将 SDS 和 RedisObject 连续分配在一块内存中,以减少内存分配和释放的开销。

  3. raw 编码 当 String 的值为字符串且长度大于 44 个字节,或者值是整数但长度大于 21 个字符时,使用 raw 编码。此时,SDS 和 RedisObject 分别分配在不同的内存块中。

最大容量限制与扩容机制

Redis 中的字符串最大容量限制为 **512MB**。这个限制是为了在保证内存效率、性能和数据结构的情况下,提供高效的数据存储和访问。512MB 的限制源于 Redis 使用的 32 位整数可以表示的最大长度,同时也考虑到性能和内存管理的需求。

扩容机制

SDS 的扩容机制根据字符串的长度分为两种情况:

  1. 小于 1MB 的扩容: 当 SDS 的长度小于 1MB 时,扩容时会增加固定的 1MB 的空间。这意味着每次扩容都会预留 1MB 的额外空间,以减少频繁的内存分配操作。

  2. 大于 1MB 的扩容: 当 SDS 的长度大于 1MB 时,扩容时会根据当前字符串的长度进行动态扩展,通常是将当前长度的两倍作为新的分配空间。这种策略可以有效地减少内存重分配的次数,并提高性能。

这种扩容机制确保了在字符串增长时,SDS 可以灵活地调整内存大小,而不会造成频繁的内存分配和释放,从而提高了性能和内存利用率。

SDS 的必要性及优点

Redis 使用自定义的 SDS(Simple Dynamic String)来表示字符串,而不是使用 C 语言原生的 char *。SDS 的优点包括:

  1. 二进制安全: SDS 可以保存包含空字符 '\0' 的字符串,而 C 语言的 char * 会将 '\0' 视为字符串的结束。

  2. 获取字符串长度的复杂度为 O(1): SDS 维护了字符串的长度信息,因此可以在 O(1) 时间内获取长度,而 C 语言的 char * 需要遍历整个字符串,复杂度为 O(N)。

  3. 减少内存重分配次数: SDS 会根据字符串的长度动态分配足够的内存空间,字符串增长时会自动扩展内存,从而减少内存重分配的次数。

  4. 避免缓冲区溢出: SDS 的设计确保了不会发生缓冲区溢出,提供了安全的 API 接口,避免了 C 语言字符数组的安全隐患。

SDS 实现原理,结合源码 SDS 的定义如下(摘自 sds.h):

c 复制代码
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, 5 msb of string length */
    char buf[];
};

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 msb of string length */
    char buf[];
};

struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 msb of string length */
    char buf[];
};

struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 msb of string length */
    char buf[];
};

struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 msb of string length */
    char buf[];
};

从源码可以看出,SDS 由两部分组成:头部和正文缓冲区。头部包含字符串的长度、分配的内存大小和类型标志位,正文缓冲区存储实际字符串内容。 SDS 根据字符串的长度选择不同的头部结构体,具体如下:

  • 当字符串长度小于 32 字节时,使用 `sdshdr5`。
  • 当字符串长度小于 256 字节时,使用 `sdshdr8`。
  • 当字符串长度小于 65536 字节时,使用 `sdshdr16`。
  • 当字符串长度小于 4GB 时,使用 `sdshdr32`。
  • 当字符串长度小于 16EB 时,使用 `sdshdr64`。

这种设计可以有效减少内存的浪费,并且在修改字符串时只需修改头部的长度信息,而不需要修改正文缓冲区,从而提高了效率。

Redis 在 `sds.c` 文件中实现了一系列操作 SDS 的函数,如 `sdsnew`、`sdscatlen`、`sdsgrowzero` 等,用于创建、拼接和扩展 SDS。这些函数会根据需要自动扩展 SDS 的内存空间,并维护好长度信息。

总结

Redis 使用自定义的 SDS 来表示字符串,相比 C 语言原生的 `char *`,SDS 提供了二进制安全、获取长度 O(1) 复杂度、减少内存重分配次数等优点。SDS 的实现原理是使用头部存储元信息,根据字符串长度选择不同的头部结构体,提高了内存利用率和修改字符串的效率。此外,Redis 将字符串的最大容量限制为 512MB,并通过灵活的扩容机制确保在字符串增长时能够有效管理内存。

相关推荐
minihuabei1 小时前
linux centos 安装redis
linux·redis·centos
monkey_meng3 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
hlsd#4 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
奶糖趣多多6 小时前
Redis知识点
数据库·redis·缓存
CoderIsArt7 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
ketil2711 小时前
Redis - String 字符串
数据库·redis·缓存
王佑辉13 小时前
【redis】延迟双删策略
redis
生命几十年3万天14 小时前
redis时间优化
数据库·redis·缓存
Shenqi Lotus15 小时前
Redis-“自动分片、一定程度的高可用性”(sharding水平拆分、failover故障转移)特性(Sentinel、Cluster)
redis·sentinel·cluster·failover·sharding·自动分片·水平拆分
YMY哈18 小时前
Redis常见面试题(二)
redis