源码阅读基于Redis4.0.9

SDS介绍

redis 127.0.0.1:6379> SET dbname redis
OK
redis 127.0.0.1:6379> GET dbname
"redis"

从上面的例子可以看到,key为dbname的值是一个字符串“redis”

Redis源码是用c写成,但并没有使用c的字符串。c的字符串有以下缺点:

  1. 没有储存字符串长度的变量,获取长度只能靠遍历字符串
  2. 扩容麻烦。没有相应保护,容易造成缓冲区溢出
  3. 更新字符串需要重新分配内存
addr value
0x0 s
0x1 t
0x2 r
0x3 1
0x4 '\0'
0x5
0x6
0x7
0x8 a
0x9 b
0xa '\0'

解释下2,3点。上图是一段连续的内存,保存了字符串"str1"和“ab”。如果我们用strcat函数,拼接一个“append”在“str1”后面,就会对“ab”产生影响。造成内存的破坏。

同样的道理,想要更新字符串,同时又不造成溢出,只能重新分配一段内存。

普通的应用程序,上面的操作是可以接受的。但是redis作为数据库,经常增删改查,加上对速度有一定需求,所以不能使用C的字符串。

我们可以在src/sds.h中找到sds的声明:

typedef char *sds;

怎么回事?redis中的sds还是char* ,那不是和C字符串一样了吗?

其实这里只是为了兼容,而每个sds字符串前都有一个sds header,保存了该sds字符串的信息

下面是sdsnew函数,用来创建一个sds字符串

/* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
} /* for example mystring = sdsnewlen("abc",3); */
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
char type = sdsReqType(initlen); //根据initlen的值计算出type类型
/* 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. */ /*给sds header分配空间*/
sh = s_malloc(hdrlen+initlen+1);
if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
/*根据type初始化sh的成员*/
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'; //字符串最后添加'\0'进行兼容,使printf可以打印sds
return s;
}

SDS header结构体

redis的SDS header结构体如下:

/* 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不被使用,剩下的只是长度的区别,成员是一样的。

  • buf[] 实际存储字符的数组
  • len 字符串长度
  • alloc 最大容量。等于len(buf)-1,因为字符串最后一位固定是'\0'
  • flags 低3位是类型,高5位保留

关于该结构体,还需要注意2点:

  1. attribute ((packed))是为了让编译器以紧凑的方式分配内存,否则编译器可能会对结构体的成员进行对齐优化。对这里不太明白的可以看看struct大小的计算
  2. 结构体的最后定义了char buf[]; 这个字段只能作为结构体的最后一个成员。C语言中被称为柔性数组,只是作为一个标记,不占用内存空间。

    如果明白了以上2点,应该能算出sizeof(sdshdr32)=4+4+1=9Byte

SDS优点

常数时间获取字符串长度

static inline size_t sdslen(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)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}

因为SDS header中保存了字符串长度,所以直接读取sdshdr->len即可,消耗常数时间

Redis源码阅读一:简单动态字符串SDS的更多相关文章

  1. redis源码学习_简单动态字符串

    SDS相比传统C语言的字符串有以下好处: (1)空间预分配和惰性释放,这就可以减少内存重新分配的次数 (2)O(1)的时间复杂度获取字符串的长度 (3)二进制安全 主要总结一下sds.c和sds.h中 ...

  2. redis 系列3 数据结构之简单动态字符串 SDS

    一.  SDS概述 Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默 ...

  3. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

  4. Redis源码阅读(四)集群-请求分配

    Redis源码阅读(四)集群-请求分配 集群搭建好之后,用户发送的命令请求可以被分配到不同的节点去处理.那Redis对命令请求分配的依据是什么?如果节点数量有变动,命令又是如何重新分配的,重分配的过程 ...

  5. Redis源码阅读(六)集群-故障迁移(下)

    Redis源码阅读(六)集群-故障迁移(下) 最近私人的事情比较多,没有抽出时间来整理博客.书接上文,上一篇里总结了Redis故障迁移的几个关键点,以及Redis中故障检测的实现.本篇主要介绍集群检测 ...

  6. Redis源码阅读(五)集群-故障迁移(上)

    Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...

  7. Redis源码阅读(三)集群-连接初始化

    Redis源码阅读(三)集群-连接建立 对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供 ...

  8. Redis源码阅读(一)事件机制

    Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可 ...

  9. 图解Redis之数据结构篇——简单动态字符串SDS

    图解Redis之数据结构篇--简单动态字符串SDS 前言     相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...

随机推荐

  1. 面向对象案例-学生信息管理系统V1.1

    1.学生类 package com.qfedu.student.entity; /** * 学生类实体 * * @author GGGXXC * */ public class Student { p ...

  2. 【JUC】CyclicBarrier和Semaphore的使用

    CyclicBarrier的使用 CyclicBarrier:可以让一组检测到一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有的屏障拦截的线程才会继续执行,线程进入屏障通过Cyclic ...

  3. 使用Docker发布blazor wasm

    Blazor编译后的文件是静态文件,所以我们只需要一个支持静态页面的web server即可. 根据不同项目,会用不同的容器编排,本文已无网关的情况下为例,一步一步展示如何打包进docker 需求 H ...

  4. Unity 游戏框架搭建 2019 (五十、五十一) 消息机制小结&MonoBehaviourSimplify 是框架?

    我们花了 5 篇文章学习了消息机制的方方面面.并且完成了一个简易消息机制,之后集成到了我们的 MonoBehaviourSimplify 里. 现在 MonoBehaviourSimplify 有一点 ...

  5. html5学习之路_002

    html块 html块元素 块元素在显示时,通常会以新行开始 如:<h1>.<p>.<ul> html内联元素 内联元素头通常不会以新行开始 如:<b> ...

  6. 基于nodejs+express+mysql+webstorm+html的 增删改查

    一.工具准备 Nodejs框架,WebStorm.Mysql服务.Navicat.此篇文章只讲项目的搭建过程,至于Nodejs,WebStorm.Mysql的下载.安装与配置网上资源很多,请自行查阅, ...

  7. 【docker系列2】docker 的前世今生

    Docker 入门,共 3 篇,将带大家进入 Docker 的世界.首先了解 Docker 的发展历程, 然后快速掌握 Docker 的基本使用: Docker 版本及内核兼容性选择是这部分的重点内容 ...

  8. Codeforces Round #646 (Div. 2)【C. Game On Leaves 题解】

    题意分析 关于这道题,意思就是两个人摘叶子,谁最后摘到编号为x的谁就赢了.既然是叶子,说明其最多只有一个分支,由于题目上说了是无向图,那就是度数小于等于的节点.也就是一步步移除度数小于等于的节点,直到 ...

  9. ES6背记手册

    ES6规范 阮一峰的ES6在线教程 在线图书--Exploring ES6 ES6 tutorials babel在线教程--https://babeljs.io/docs/en/learn.html ...

  10. 和付费网盘说再见,跟着本文自己起个网盘(Java 开源项目)

    本文适合有 Java 基础知识的人群,跟着本文可学习和运行 Java 网盘项目. 本文作者:HelloGitHub-秦人 HelloGitHub 推出的<讲解开源项目>系列. 今天给大家带 ...