Redis处理字符串的方式(SDS)

Redis是一个使用ANSI C编写的开源内存数据结构存储(in-memory data structure store),但是Redis并没有直接使用C语言传动的字符串表示,而是自己构建了一种名为简单动态字符串(Simple Dynamic Strings, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。

References:

Redis字符串的实现包含在sds.c中。该实现已经是独立的库:https://github.com/antirez/sds由于迭代比较快,最新的版本和之前版本相比区别较大。
定义在sds.h中的C结构体sdshdr表示一个Redis字符串:

struct sdshdr {
    long len; // buf[]已使用字节数(SDS的长度), 这使得获取Redis字符串长度是O(1)操作
    long free; // buf[]剩余可用字节数
    char buf[]; // 存储实际的字符串
};

lenfree可以被认为是保存了buf字符数组的元数据。看下图示例:
Snipaste_2021-04-22_20-31-42.png

SDS遵循C字符串以空字符结尾的惯例,保留空字符的1字节空间不计算在SDS的len属性里,并且为空字符串分配额外的1字节空间,以及添加空字符到字符串末尾等操作都是由SDS函数自动完成的。遵循空字符结尾可以直接重用一部分C字符串函数。

SDS和C字符串比较有这些特点:

  • 二进制安全(Binary-safe)
    C字符串中的字符必须符合某种编码(如ASCII),并且字符串里不能含有空字符,这些限制使得C字符串只能保存文本数据,而不能保存图片,音频等二进制数据。SDS使用len属性判断字符串是否结束,SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf里的数据,程序不会做任何修改,写入时什么样,读出来就是什么样,因此Redis可以保存任意格式的二进制数据。
  • 常数复杂度获取字符串长度
    C字符串不记录自身长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,知道遇到空字符为止,这个操作复杂度为O(N)。对于SDS来说,只要访问len属性就能立刻知道长度,设置和更新SDS由API在执行时自动完成。通过使用SDS,Redis将获取字符串长度的复杂度从O(N)降到了O(1),确保这个操作不会称为Redis的性能瓶颈。
  • 减少修改字符串长度时所需的内存重分配次数
    C字符串底层实现总是一个N+1长的数组,每次修改长度都需要进行内存的重新分配。SDS通过空间预分配和惰性释放减少内存分配操作。其实就是一个空间换时间的操作,C字符串非常精确的控制内存的使用,SDS会多消耗一些内存空间,但是带来的性能提升非常巨大。
  • 杜绝缓冲区溢出
    C字符串不记录长度带来的另一个问题是容易造成缓冲区溢出(buffer overflow)。例如<string.h>/strcat函数可以将src字符串拼接到dest的末尾:char * strcat ( char * destination, const char * source );,如果dest的空间不够就会产生缓冲区溢出,如Segmentation fault这种错误。而SDS的API会自动扩充空间,也就不会出现溢出的问题。

了解了SDS,再看看Redis中相关的使用:
首先是Redis的key,在官方文档里有这么两句

Redis keys are binary safe, this means that you can use any binary sequence as a key, from a string like "foo" to the content of a JPEG file. The empty string is also a valid key.

Since Redis keys are strings, when we use the string type as a value too.

简单翻译:Redis的key是二进制安全的,可以使用任意二进制序列作为key,字符串甚至是图片。
所以Redis的key事实上是SDS。
然后再看下二进制安全,SDS保存字节,也就是说Redis不处理编码,编码全部由客户端协商处理。看下面首先使用UTF-8编码连接SSH,然后操作Redis
Snipaste_2021-04-23_18-48-45.png

Redis保存的是字节信息,UTF-8编码下“这破站”每个字占3字节
Snipaste_2021-04-23_18-57-58.png

然后切换编码为GBk并重新连接SSH,再操作Redis客户端:
Snipaste_2021-04-23_19-05-30.png

同样的字编码不同,存储的内容也不一样
Snipaste_2021-04-23_19-02-00.png

说Redis是二进制安全的就是因为Redis本身不会修改数据,Redis只会原原本本的将数据保存下来,至于解码这些这些对数据的操作完全交由客户端之间去协商解决。

标签: none

添加新评论

ali-01.gifali-58.gifali-09.gifali-23.gifali-04.gifali-46.gifali-57.gifali-22.gifali-38.gifali-13.gifali-10.gifali-34.gifali-06.gifali-37.gifali-42.gifali-35.gifali-12.gifali-30.gifali-16.gifali-54.gifali-55.gifali-59.gif

加载中……