Redisbook学习笔记(1)sds

2014了休息了10多天没有写博客了不能把刚刚培养的习惯又荒废了那么就开始新年新旅途吧数据库


Sds Simple Dynamic String简单动态字符串是Redis 底层所使用的字符串表示它被用
在几乎全部的Redis 模块中。
本章将对sds 的实现、性能和功能等方面进行介绍并说明Redis 使用sds 而不是传统C 字符
串的缘由。数组

 

Sds 在Redis 中的主要做用有如下两个缓存


一、 实现字符串对象StringObject安全

Redis 是一个键值对数据库key-value DB数据库的值能够是字符串、集合、列表等多种类
型的对象而数据库的键则老是字符串对象。服务器

在Redis 中
一个字符串对象除了能够保存字符串值以外还能够保存long 类型的值因此为了严谨起见
这里须要强调一下当字符串对象保存的是字符串时它包含的才是sds 值不然的话它就
是一个long 类型的值。app


举个例子如下命令建立了一个新的数据库键值对这个键值对的键和值都是字符串对象它
们都包含一个sds 值ide

wKioL1LRSOjBIprEAABoVcl19hE355.jpg

如下命令建立了另外一个键值对它的键是字符串对象而值则是一个集合对象性能

wKiom1LRSVvAl03tAABzshWnTVw497.jpg

 

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
内部发生了什么

wKioL1LRTYGwO06uAACjbrPMKfM661.jpg

首先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 

wKiom1LRTbzyk6uXAAB-61rvtIo552.jpg

再次执行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 会为追加操做进行优化加快追加操做的速度并下降内存分配的次数代价是多占用了一些内存并且这些内存不会被主动释放。

相关文章
相关标签/搜索