编辑
2023-11-11
Redis源码阅读
00
请注意,本文编写于 546 天前,最后修改于 240 天前,其中某些信息可能已经过时。

Redis的SDS(Simple Dynamic String)是Redis中使用最多的基础数据结构之一。它具有以下特色:

  1. 动态扩展内存:SDS可以动态地扩展内存,允许字符串的内容进行修改和追加。这与其他语言中的字符串类型不同,它们通常分为可变和不可变两种类型。

  2. 二进制安全:SDS可以存储任意二进制数据,而不仅仅是可打印字符。它是二进制安全的,意味着它可以存储任意字节的数据,而不会因为特殊字符而导致数据损坏。

  3. 与传统C语言字符串兼容:SDS与传统的C语言字符串类型兼容,可以在需要传入C语言字符串的地方使用SDS。它的类型定义与C语言字符串相同,都是char*类型。

SDS的数据结构定义如下:

c
typedef char *sds; /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 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 unused bits */ 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 unused bits */ 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 unused bits */ 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 unused bits */ char buf[]; };

其中sdshdr5已经弃用。

c
__attribute__ ((__packed__))

告诉编译器不需要字节对齐。省空间。

SDS 之所以设计不同的结构头,是为了能灵活保存不同大小的字符串,从而有效节省内存空间。 否则,假设 SDS 都设计一样大小的结构头,比如都使用 uint64_t 类型表示 len 和 alloc, 那么假设要保存的字符串是 10 个字节,而此时结构头中 len 和 alloc 本身就占用了 16 个 字节了,比保存的数据都多了。所以这样的设计对内存并不友好,也不满足 Redis 节省内 存的需求。

SDS的结构由两部分组成:

  1. Header:包含字符串的长度(len)、最大容量(alloc)和标志位(flags)。不同长度的字符串使用不同大小的header,以节省内存。
  2. 字符数组:长度为最大容量+1。有效的字符串数据长度通常小于最大容量。在字符串数据之后,还有一些未使用的字节和一个NULL结束符,以与传统C字符串兼容。

以下是flags的值类型

c
// 定义SDS的类型常量 #define SDS_TYPE_5 0 // 5字节类型 #define SDS_TYPE_8 1 // 8字节类型 #define SDS_TYPE_16 2 // 16字节类型 #define SDS_TYPE_32 3 // 32字节类型 #define SDS_TYPE_64 4 // 64字节类型 // 定义SDS类型的掩码和位数 #define SDS_TYPE_MASK 7 // 类型掩码,用于提取SDS的类型 #define SDS_TYPE_BITS 3 // 类型位数,用于表示SDS的类型所需的二进制位数 // 注释: // SDS是Redis中的简单动态字符串(Simple Dynamic String)实现。 // 宏定义中的SDS_TYPE_5到SDS_TYPE_64表示不同字节长度的SDS类型, // 其中SDS_TYPE_MASK用于提取SDS的类型信息, // SDS_TYPE_BITS表示SDS类型所需的二进制位数。

还有获取sds所属结构体的宏

c
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
  • SDS_HDR_VAR(T,s) 宏定义用于定义并初始化一个名为 sh 的指针变量,该变量指向类型为 sdshdr##T 的结构体,它的值为 (s) - (sizeof(struct sdshdr##T))
  • 参数 T 是一个占位符,用于在宏定义中拼接出特定类型的结构体名。
  • 参数 s 是一个指针,表示 SDS 的起始地址。
c
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
  • SDS_HDR(T,s) 宏定义用于返回一个类型为 sdshdr##T 的结构体指针,该指针指向 SDS 的头部。
  • 参数 T 是一个占位符,用于在宏定义中拼接出特定类型的结构体名。
  • 参数 s 是一个指针,表示 SDS 的起始地址。
c
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
  • SDS_TYPE_5_LEN(f) 宏定义用于计算 SDS 类型为 SDS_TYPE_5 时的长度。
  • 参数 f 是一个字节,表示 SDS 的 flags 字节,其中包含了类型信息。
  • >> 是右移操作符,表示将 f 右移 SDS_TYPE_BITS 位,相当于将 f 的低 SDS_TYPE_BITS 位去掉,只保留高位的类型信息。

这些宏定义在 Redis 源码中用于简化对 SDS 结构体的操作。SDS_HDR_VARSDS_HDR 宏定义用于将指向 SDS 字符串的指针转换为指向 SDS 头部结构体的指针,方便获取头部信息。SDS_TYPE_5_LEN 宏定义则用于从 flags 字节中提取 SDS 类型为 SDS_TYPE_5 时的长度信息。

一些常用的函数的函数

c
static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; // 获取SDS的前一个字节,即flags字节,用于获取SDS的类型信息 switch(flags&SDS_TYPE_MASK) { // 使用掩码将flags字节与SDS_TYPE_MASK进行与操作,获取SDS的类型 case SDS_TYPE_5: // 如果类型为SDS_TYPE_5 return SDS_TYPE_5_LEN(flags); // 返回SDS_TYPE_5_LEN宏定义的值,用于计算SDS的长度 case SDS_TYPE_8: // 如果类型为SDS_TYPE_8 return SDS_HDR(8,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度 case SDS_TYPE_16: // 如果类型为SDS_TYPE_16 return SDS_HDR(16,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度 case SDS_TYPE_32: // 如果类型为SDS_TYPE_32 return SDS_HDR(32,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度 case SDS_TYPE_64: // 如果类型为SDS_TYPE_64 return SDS_HDR(64,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度 } return 0; // 默认返回0,表示长度为0 } static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { return 0; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); return sh->alloc - sh->len; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); return sh->alloc - sh->len; } } return 0; } static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len = newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->len = newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->len = newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->len = newlen; break; } } static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len += inc; break; case SDS_TYPE_16: SDS_HDR(16,s)->len += inc; break; case SDS_TYPE_32: SDS_HDR(32,s)->len += inc; break; case SDS_TYPE_64: SDS_HDR(64,s)->len += inc; break; } } /* sdsalloc() = sdsavail() + sdslen() */ static inline size_t sdsalloc(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->alloc; case SDS_TYPE_16: return SDS_HDR(16,s)->alloc; case SDS_TYPE_32: return SDS_HDR(32,s)->alloc; case SDS_TYPE_64: return SDS_HDR(64,s)->alloc; } return 0; } static inline void sdssetalloc(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: /* Nothing to do, this type has no total allocation info. */ break; case SDS_TYPE_8: SDS_HDR(8,s)->alloc = newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->alloc = newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->alloc = newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->alloc = newlen; break; } }

除此之外还有这些函数

image.png 一个sds实例怎么创建的

c
sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); } sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (init==SDS_NOINIT) init = NULL; else if (!init) memset(sh, 0, hdrlen+initlen+1); s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; switch(type) { case SDS_TYPE_5: { *fp = type | (initlen << SDS_TYPE_BITS); break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } if (initlen && init) memcpy(s, init, initlen); s[initlen] = '\0'; return s; }

SDS的使用示例:

c
// 创建一个SDS字符串 sds s = sdsnew("Hello"); // 追加字符串 s = sdscat(s, " World"); // 获取字符串长度 size_t len = sdslen(s); // 打印字符串 printf("%s\n", s); // 释放内存 sdsfree(s);

通过SDS,我们可以方便地进行字符串的操作,包括创建、追加、获取长度和释放内存等。


Learn more:

  1. Redis内部数据结构详解(2)--sds - 铁蕾的个人博客
  2. Redis SDS详解 - 知乎
  3. Redis从入门到精通之底层数据结构SDS(简单动态字符串)详解-阿里云开发者社区

本文作者:yowayimono

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!