看完这个还不了解redis的SDS,半夜你来扒我家墙头😄

1,082 阅读3分钟

这是我参与更文挑战的第1天,活动详情查看: 更文挑战

概述:

  我们都知道redis底层是用C语言实现的,而C的字符串是以‘\0’来判断结束的,是一个对特殊字符存储不安全的。那redis是怎么做到存储二进制安全的呢?

重头戏-redis中的字符串

  redis中还是有用到C中字符串的地方,比如一些不需要修改的字面量一些reids要输出的日志信息等。而需要修改的字符串redis抽象成了我们的SDS对象:简单动态字符串(simple dynamic string,SDS)。这部分的源码对应在这两个文件中

sds.h

sds.c

SDS的定义

每个 sds.h/sdshdr 结构表示一个 SDS 值,结构如下:

struct sdshdr {

    // 记录 buf 数组中已使用字节的数量
    // 等于 SDS 所保存字符串的长度
    int len;
    // 记录 buf 数组中未使用字节的数量
    int free;
    // 字节数组,用于保存字符串,
    char buf[];

};

我们的buf中存放的就是我们真正的按照\0结束的字符串内容(在3.2版本之后此处改成了一个指针会根据不同长度指向不同的结构),len是我们的字符串内容的长度。还可以看到有一个free的子段,这个子段的作用先卖个关子下边说😄

SDS中free的作用

在c语言中使用不当经常会有内存溢出的问题,导致我们的字符串被覆盖,而redis是怎么避免这种问题的出现的呢?

首先我们在redis中插入一个key:

> set demo hello;

这个时候的SDS对象是:

struct sdshdr {
    int len; = 5
    int free; = 0
    char buf[]; = 'hello'

};

> set demo 'hello world';

这个时候的SDS对象是:

struct sdshdr {
    int len; = 11
    int free; = 11
    char buf[]; = 'hello world'

};
> set demo 'hello world wang';
struct sdshdr {
    int len; = 16
    int free; = 6
    char buf[]; = 'hello world wang'

};

我们在set hello world之后 redis会根据我们的free子段去判断需不需要向操作系统申请内存,如果free够用则不申请。这个时候就涉及到redis的内存分配策略了,为了避免多次向操作系统申请内存的开销,redis在每次申请都会按照当前len的两倍去申请(当len于1M时候就只会多申请1M),也就可以解释我们的free为什么是11了,这就是redis的空间预分配。

我们可以看下redis-5源码涉及到内存分配的策略,扩容是这个方法XDM可以去看看 sds sdsMakeRoomFor(sds s, size_t addlen)

还有一个惰性空间释放的概念会涉及到我们的free子段,当我们将redis的字符串内容减少的时候,redis并不会立马释放内存而是仅仅修改free子段,以便下次使用。

总结

  • 因为SDS有存储字符串长度len,所以我们获取redis字符串长度是一个O(1)的操作。
  • len的存在避免了内存溢出的问题
  • 通过内存分配策略,减少了字符串修改时候的内存分配次数