您现在的位置是:网站首页 / RedisRedis

直面Redis-字符串

李先生2021-10-10 267人围观

简介 本文介绍了Redis的支持的数据结构-字符串,从源码角度上介绍了SDS的结构。当Redis扩充字符串,缩减字符串内部结构的变化。最后介绍了空间预分配和惰性回收机制

概念

从之前的水文了解到Redis支持了好几种数据结构是吧,本次就从简单的string开始。

虽然我们知道Redis是用C语言开发的,但是Redis内部使用的字符串却不是C语言的字符串。Redis的字符串是自己定制的一种简单动态字符串,简称SDS(simple dynamic string,SDS),并且保留了部分C语言下的字符串特性,比如以\0结尾,那是不是Redis就没有用到C语言的字符串呢?其实在比如Redis打印日志是用的是C语言原生的字符串的,除此之外都是使用的SDS,比如:

static int db_setfenv (lua_State *L) {
  luaL_checktype(L, 2, LUA_TTABLE);
  lua_settop(L, 2);
  if (lua_setfenv(L, 1) == 0)
    luaL_error(L, LUA_QL("setfenv")
                  " cannot change environment of given object");
  return 1;
}

结构定义

上面噼里啪啦胡乱讲了一通概括了一句话,Redis使用的是带有C原生字符串的某些特性的自定义数据结构,叫做简单动态字符串SDS。

那么既然C语言有了字符串了,为啥Redis还要造个寂寞火箭呢?来,接着看

如果在C语言下我要计算一个字符串长度是怎么样的呢?来段最简单的C代码

int main() {
    int len = strlen("自由书,一个程序爱好者的天堂");
    int len2 = strlen("自由书,一个程序爱好者\0的天堂");
    printf("长度: %d\n", len);
    printf("长度2: %d\n", len2);
    return 0;
}

打印输出的结果是:
  长度: 42
  长度2: 33

有没有觉得奇怪呢?两个字符串只是一个在中间插入\0长度差这么大呢?其实C语言的字符串长度计算是从字符串第一个字符开始计数,直到遇到\0后结束。想想10W次/秒读写的Redis如果是这样计算长度会有这么高的效率吗?其次更重要的是安全性,我给各位官老爷安排个演示

字符串演示
字符串演示

看到了吗,我们在操作Redis时可能会意外的插入\0,然后计算字符串长度时Redis的字符串并不会像原生一样以\0结尾。保证了安全性,到这里裤子都脱到磕膝盖了,小伙伴是不是想看看Redis的SDS是什么样的结构了,先看下老版本的SDS

# 在src/sds.h里有定义
/*
 * Redis字符串数据结构
 */
struct sdshdr {
    // 字符串长度
    int len;
    // 还未分配的长度
    int free;
    // 字符串数组
    char buf[];
};

可以看到在Redis的SDS中是定义了字符串的长度的,所以当客户端获取值字符串的长度时是直接获取返回的,而不是每次计算的。

我们针对字符串的新增和修改来画图演示:

新增字符串

比如我们执行新创建一个key,然后返回长度

> set zys 'redis'
> strlen zys
"5"

设置一个key为zys的字符串
设置一个key为zys的字符串

此时redis会申请分配一个长度为5+1的数组,为了保留C字符串的特性以\0结尾,但是设置给sdshdr的len=5,free=0,buf就等于字符串数组。

修改字符串长度

上面的图是redis设置一个字符串的过程,下面我们来展示下修改追加字符串,把redis修改为hellow redis, 执行以下命令

> set zys 'hellow redis'
> strlen zys
"12"

修改字符串的值
修改字符串的值

上图就是把redis修改为hellow redis后的变化,可以看到len=12,free=12。在Redis内部会扩容,在这里具体是分配字符串长度的两倍。

空间管理策略

我们在上面演示了Redis新增和修改字符串内部的变化,但是其实还没演示完,因为还有字符串变少的情况和为什么要留free个空位呢?

空间预分配

空间预分配是Redis在操作字符串扩长时,会分配字符串所需的空间,并且还会分配一定的空余空间,用于后续使用。具体分配多少Redis是有计算公式的

  1. 如果新设置的值的长度小于1M,那么Redis会分配和字符串同等长度的空余空间,此时SDS的len和free是相等的。就拿上面的例子来讲,修改过后字符串为hellow redis 长度为12,那么len=12,然后会分配同等长度的空余空间,所以free=12,那么此时在buf的长度是12+12+1字节,别忘记了还有个\0
  2. 如果新设置的值的长度大于1M,那么Redis就分配1M的空余空间,比如修改过后的长度是2M,那么此时len=2M,free=1M,那么buf里面的长度其实是1M+2M+1byte。

那么问题来了,就按上面的图来操作字符串,现在字符串是hellow redis 此时len=12,free=12,那我把字符串修改为hellow ziyoushu, len和free该等于多少呢?没错,len=15 ,free=9。这样来看redis频繁的操作字符串其实内部并不是频繁的创建和扩容的,而是预分配的策略来保证空间可用的

空间惰性回收

惰性回收其实是比如操作字符串缩减时,Redis并不会马上回收多余的空间,而是把剩余的空间记录给free,为的后续可能存在扩充字符串时使用。

  • 11 点赞
  • 1 收藏
  • 分享

作品评论

# 本栏推荐

直面Redis-概念