sds(simple dynamic string) 简单动态字符串,是redis内部存储字符串类型的数据结构,是对原生c语言中char[]的扩展和封装. sdshdr数据结构(v3.0及以前) sds 是如何使用的. set命令 在执行set key redis命令时,sds存储字符串的过程 1. 综上所述,sds有如下优点: 1. 可动态扩展内存,sds字符串其内容可以修改,追加. 2. 二进制安全,sds能存储任意二进制数据,而不仅仅是可打印字符. 4. 与传统的c语言字符串类型兼容. sds的缺点及优化 缺点: 1. redis的key也使用sds作为存储数据结构,但key是不会有更改操作的,这就造成了空间的浪费. 2. sds中的len,free的类型是
然而,很少有人能说清楚SDS字符串到底是什么,为什么使用SDS字符串比使用C语言字符串效率要高。 什么是SDS SDS全拼为:simple dynamic string,解释为:简单动态字符串。 上图展示了一个SDS实例,len表示该SDS保存了一个5字节长度(不包含结束符)的字符串,free表示该SDS还有5个字节的未使用空间,buf是一个char类型的数组,保存了该SDS所存储的字符串值 与C语言不同,当SDS API需要对SDS进行修改时,API会先检查SDS当前剩余空间是否满足修改之后所需的空间,如果不满足的话API会自动将SDS的空间扩展至修改之后所需空间大小,然后再执行实际的修改操作 如果对SDS修改之后,SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。
当然 SDS 也提供了 SDS 显式调用,真正的释放未使用的空间。 a b c d3.1.1 sdshdr 巧妙的结构设计SDS 的相关代码主要在下面两个文件:sds. h:头文件sds. c:源文件SDS 定义在 sds. h 中,为了兼容 C 风格的字符串,给 char 来获取动态字符串对应的字符串类型#define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE _32 3 #define SDS_TYPE_64 4 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3 #define SDS_HDR_VAR(T 类型一共有这些:#define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #
如果内存分配错误,会导致很严重的后果,就算内存分配没问题,频繁的内存分配也是非常耗费时间的,所以这些都是应该去避免的 惰性空间释放策略 在SDS中首先用到了惰性空间释放策略,惰性空间释放用于优化SDS的字符串缩短操作 当要缩短SDS保存的字符串时,程序并不立即使用内存充分配来回收缩短后多出来的字节,而是使用表头的free成员将这些字节记录起来,并等待将来使用。 源码如下 void sdsclear(sds s) { //重置sds的buf空间,懒惰释放 struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr return SDS_TYPE_16; #if (LONG_MAX == LLONG_MAX) if (string_size < 1ll<<32) //2^32-1 return SDS_TYPE _32; return SDS_TYPE_64; #else return SDS_TYPE_32; #endif }
[Redis 源码解析 1:字符串 SDS ] Redis 中字符串都用自定义的结构**简单动态字符串(Simple Dynamic Strings,SDS),而不是C语言的字符串。 我们来看一下新版本的 SDS 结构。 的初始化 SDS的初始化如下,开始创建时SDS分配的buf空间大小与字符串长度一致 /* Create a new sds string starting from a null terminated s,否则创建新的SDS返回 sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) { void *sh, *newsh; // 获取 SDS 缩容,不释放多余的内存,下次使用可直接复用这些内存
源代码 sds sdscat(sds s ,const char *t) { return sdscatlen(s,t,strlen(t)); } sds sdscatsds(sds s,const sds t) { return sdscatlen(s,t,sdslen(t)); } 说明 这两个函数的功能几乎是一样的:把字符串t添加到字符串s的后面,并且长度为t的长度。 疑惑 但是为什么要把char *和sds字符串区别开来呢?sds的定义就是插入类型的指针。这样做的目的是什么呢?是出于什么考虑呢?
02 介绍 Redis没有直接使用C语言传统的字符串来表示(以空字符串结尾的字符数组),而是自己构造了一种名为简单动态字符串SDS。 之前看的String类型的数据结构底层就是用SDS实现的。 len为5,表示这个sds长度为5个字节。 free为2,表示这个sds还有2个字节未使用的空间。 如果SDS表头len成员小于1MB(1024X1024),就分配和len长度相同的未使用空间。 如果SDS表头len成员大于等于1MB(1024X1024),就分配1MB的未使用空间。 sds sdsMakeRoomFor(sds s, size_t addlen) { //对 sds 中 buf 的长度进行扩展 struct sdshdr *sh, *newsh; size_t 的源码,如果要看他具体每个操作的步骤,就要看具体的文件了sds.c和sds.h。
节点 IP 服务
sds sds sdsempty(void); //清空sds操作 size_t sdslen(const sds s); //获取sds的长度 sds sdsdup(const sds s); //sds的复制方法 void sdsfree(sds s); //sds的free释放方法 size_t sdsavail(const sds s); //判断sds获取可用空间 ); sds sdscat(sds s, const char *t); //sds连接上char字符 sds sdscatsds(sds s, const sds t); //sds连接上 sds sds sdscpylen(sds s, const char *t, size_t len); //字符串复制相关 sds sdscpy(sds s, const char *t); / void sdsclear(sds s); //字符串清空操作 int sdscmp(const sds s1, const sds s2); //sds比较函数 sds *sdssplitlen
sds 在redis中,存储字符串的结构称为 sds (Simple Dynamic String) 简单动态字符串 在源码sds.h中定义如下: typedef char *sds; /* Note 存储结构 在sds中,8的存储结构如下: typedef char *sds; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len ); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len = newlen; ; case SDS_TYPE_32: SDS_HDR(32,s)->len = newlen; break; case ,sds的指针位置-结构体长度=sdshdr的指针位置: #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)
源程序 sds sdscatlen(sds s, const void *t, size_t len) { struct sdshdr *sh; size_t curlen = sdslen(s); 说明 Append the specified binary-safe string pointed by 't' of 'len' bytes to the end of the specified sds 该函数的功能就是在原sds字符串的基础上添加len长度的字符串。新添加的字符串的头指针是t,添加到s的位置。
EMC公司在当年的EMC World发布大会上也发布了SDS战略,引发了业界对SDS的大讨论,SDS迅速成为存储业界的研究热点。 而SDS的概念则是最近几年随着华为、杉岩数据等企业产品的逐渐成熟而兴起的。 SDS是个啥? 2012年8月,VMware首次提出软件定义存储的概念。 实际上,SDS的定义出现至今已经三年多了,但仍没有统一的标准。贡献最大的SNIA认为SDS允许异构的或者专有的平台。必须满足的是,这个平台能够提供部署和管理其虚拟存储空间的自助服务接口。 SDS对于初学者需要注意啥? 尽管SDS概念很简单,但过渡到技术既复杂又具有挑战性。 “虽然SDS增长势头强劲,但仍存在一些问题。”栗蔚表示。
二、SDS的优势 1、O(1)时间复杂度获取字符串长度 SDS内部维护着一个字符串长度的len变量,可以直接读取,时间复杂度为O(1)。 图2 与C不同的是,SDS的空间预分配策略可以避免缓冲区溢出发生, 当需要对SDS进行操作时,首先会检查当前空间是否满足需求,不足则扩展当前分配空间。内存检查相对于C变成了内部预置操作。 针对此弊端,Redis 在SDS内存配置策略上采用了空间预分配+惰性删除相结合的策略。 a)空间预分配: 空间预分配用于优化SDS字符扩展操作。 如下,图1执行图2操作后SDS变更为: ? SDS相关的功能方法会以二进制的形式来操作SDS存储的数据,没有任何中间操作,存储最原始的数据,因此不会有字符层面的因素影响。 SDS可以保存任何源的二进制数据,字符、图片、文件或者序列化的对象等等。
源程序 sds sdsMakeRoomFor(sds s,size_t addlen) { struct sdshdr *sh,*newsh; sizt_t free = sdsavail sdslen(s); sh = (void*)(s-(sizeof(struct sdshdr))); newlen = (len+addlen); if(newlen < SDS_MAX_PREALLOC ) newlen *= 2; else newlen += SDS_MAX_PREALLOC; newsh = realloc(sh,sizeof(struct if(newsh == NULL) return NULL; newsh->free = newlen - len; return newsh->buf; } 这个函数是用来为sds 仅仅增加free的长度,不会改变sds字符串的长度。 这个函数比较难理解的地方是:扩容的幅度这里,为什么扩新长度的2倍
源程序 //将sds字符串置空 void sdsclear(sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr ->free += sh->len; sh->len = 0; sh->buf[0]='\0'; } 这个函数挺简单的,没有什么特别之处,功能就是将原来的sds
源函数 static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr 源函数 static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))) ; >return sh->free; } 这是描述当前sds字符串中还剩下的空间量的函数。
由于s2[-1] == 0x02 == SDS_TYPE_16,因此s2的header类型是sdshdr16。 下面的部分是sds的实现源代码: sds一共有5种类型的header。 _5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE_64 4 #define #define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE ,而SDS_TYPE_5扩容会造成内存的分配,所以使用SDS_TYPE_8 进行判定 SDS字符串的长度为:hdrlen+initlen+1 -> sds_header的长度 + 初始化长度 + 1 ( 位的操作系统上根据判断小于2^32分配sds32,否则分配sds64。
源程序 void sdsIncrLen(sds s,int incr) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); unsigned int)(-incr)); sh->len += incr; sh->free -= incr; s[sh->len] = '\0'; } 这个函数用来计算调整sds 每次对sds字符串经过操作之后,字符串的len和free的大小都会变动。
源代码 sds sdsnewlen(const void *init, size_t initlen) { >struct sdshdr *sh; >if (init) { > sh = zmalloc 修改后的sdsnewlen() sds mysdsnewlen(const void *init,size_t initlen) { struct sdshdr *sh; >if(init) { >
本文是该系列的第一篇文章,从以下维度对SDS源码进行分析。 Part1SDS是什么? 2SDS五种结构 下图是源码src/sds.h中定义的5种结构体类型,为啥一个SDS还要搞这么多类型呢?定义成上面的结构不就行了嘛? #define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE = '\0') return tmp; } Part3SDS实现 SDS实现涵盖的知识点不外乎SDS的创建、修改、查找和释放等功能,实现代码在src下的sds.c文件中。 拼接 sds暴露给外部使用的拼接接口非常简洁,入参是要拼接的两个sds,返回拼接后的sds。