本文共 2491 字,大约阅读时间需要 8 分钟。
2014了休息了10多天没有写博客了不能把刚刚培养的习惯又荒废了那么就开始新年新旅途吧
Sds Simple Dynamic String简单动态字符串是Redis 底层所使用的字符串表示它被用在几乎所有的Redis 模块中。本章将对sds 的实现、性能和功能等方面进行介绍并说明Redis 使用sds 而不是传统C 字符串的原因。
Sds 在Redis 中的主要作用有以下两个
1、 实现字符串对象StringObjectRedis 是一个键值对数据库key-value DB数据库的值可以是字符串、集合、列表等多种类
型的对象而数据库的键则总是字符串对象。在Redis 中
一个字符串对象除了可以保存字符串值之外还可以保存long 类型的值所以为了严谨起见这里需要强调一下当字符串对象保存的是字符串时它包含的才是sds 值否则的话它就是一个long 类型的值。 举个例子以下命令创建了一个新的数据库键值对这个键值对的键和值都是字符串对象它们都包含一个sds 值以下命令创建了另一个键值对它的键是字符串对象而值则是一个集合对象
2.、在Redis 程序内部用作char* 类型的替代品
在Redis 中客户端传入服务器的协议内容、aof 缓存、返回给客户端的回复等等这些重要的内容都是由都是由sds 类型来保存的。
在C 语言中字符串可以用一个\0 结尾的char 数组来表示。
比如说hello world 在C 语言中就可以表示为"hello world\0" 。这种简单的字符串表示在大多数情况下都能满足要求但是它并不能高效地支持长度计算和追加append这两种操作 每次计算字符串长度strlen(s)的复杂度为(N) 。对字符串进行N 次追加必定需要对字符串进行N 次内存重分配realloc。
考虑到这两个原因Redis 使用sds 类型替换了C 语言的默认字符串表示sds 既可以高效地
实现追加和长度计算并且它还是二进制安全的。
sds的实现由以下两个部分组成
typedef char *sds;struct sdshdr{//buf已占用长度int len;//buf剩余可用长度int free;//实际保存字符串数据的地方char buf[];};
作为例子以下是新创建的同样保存hello world 字符串的sdshdr 结构
struct sdshdr {len = 11;free = 0;buf = "hello world\0"; // buf 的实际长度为len + 1};
通过len 属性sdshdr 可以实现复杂度为(1) 的长度计算操作。
另一方面通过对buf 分配一些额外的空间并使用free 记录未使用空间的大小sdshdr 可以让执行追加操作所需的内存重分配次数大大减少。为了易于理解我们用一个Redis 执行实例作为例子解释一下当执行以下代码时Redis
内部发生了什么首先SET 命令创建并保存hello world 到一个sdshdr 中这个sdshdr 的值如下
struct sdshdr {len = 11;free = 0;buf = "hello world\0";}
当执行APPEND 命令时相应的sdshdr 被更新字符串" again!" 会被追加到原来的
"hello world" 之后struct sdshdr {len = 18;free = 18;// 空白的地方为预分配空间共18 + 18 + 1 个字节buf = "hello world again!\0 ";}
注意当调用SET 命令创建sdshdr 时sdshdr 的free 属性为0 Redis 也没有为buf 创建
额外的空间——而在执行APPEND 之后Redis 为buf 创建了多于所需空间一倍的大小。 在这个例子中保存"hello world again!" 共需要18 + 1 个字节但程序却为我们分配了18 + 18 + 1 = 37 个字节——这样一来如果将来再次对同一个sdshdr 进行追加操作只要追加内容的长度不超过free 属性的值那么就不需要对buf 进行内存重分配。 比如说执行以下命令并不会引起buf 的内存重分配因为新追加的字符串长度小于18再次执行APPEND 命令之后msg 的值所对应的sdshdr 结构可以表示如下
struct sdshdr {len = 25;free = 11;// 空白的地方为预分配空间共18 + 18 + 1 个字节buf = "hello world again! again!\0 ";}
在目前版本的Redis 中SDS_MAX_PREALLOC 的值为1024 * 1024 也就是说当大小小于
1MB 的字符串执行追加操作时sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间当字符串的大小大于1MB 那么sdsMakeRoomFor 就为它们额外多分配1MB 的空间。
Note: 这种分配策略会浪费内存吗
执行过APPEND 命令的字符串会带有额外的预分配空间这些预分配空间不会被释放除非该字符串所对应的键被删除或者等到关闭Redis 之后再次启动时重新载入的字符串对象将不会有预分配空间。因为执行APPEND 命令的字符串键数量通常并不多占用内存的体积通常也不大所以这一般并不算什么问题。另一方面如果执行APPEND 操作的键很多而字符串的体积又很大的话那可能就需要修改Redis 服务器让它定时释放一些字符串键的预分配空间从而更有效地使用内存。
小结
Redis 的字符串表示为sds 而不是C 字符串以\0 结尾的char*。
对比C 字符串sds 有以下特性– 可以高效地执行长度计算strlen– 可以高效地执行追加操作append– 二进制安全 sds 会为追加操作进行优化加快追加操作的速度并降低内存分配的次数代价是多占用了一些内存而且这些内存不会被主动释放。
转载地址:http://qtnoa.baihongyu.com/