Redis字符串,你真的了解吗?

2023-03-24 00:00:00 数据 操作 对象 字符串 编码

原文链接:https://mp.weixin.qq.com/s/bujgyEI6idmh7DOghzG-cQ

今天我们就来开启我们的连载章——Redis字符串对象String,这个对象看起来挺简单,但是Redis做了一些优化,很多细节都需要了解,同时呢,String作为Redis基础的数据对象,也是面试中的高频考点。

1.是什么?

String就是字符串,它是Redis中基本的数据对象,大为512MB。

2.适用场景

一般可以用来存字节数据、文本数据、序列化后的对象数据等。

3.常用操作

常用操作聚焦于创建、查询、更新和删除。
创建,即产生一个字符串对象数据,可以用SET、SETNX。
查询操作可以用GET,如果想一次获取多个,可以用MGET。
而更新的话,其实也是用SET来更新的。
删除就是针对String对象本身的销毁,用DEL命令。

3.1 写操作

3.1.1 SET

语法:SET key value
功能:设置一个key的值为特定的value,成功则返回OK。
String对象的创建或者更新都是该命令。
127.0.0.1:6379> SET strniuniu catOK


3.1.2 SETNX

语法:SETNX key value
功能:用于在指定的key不存在时,为key设置指定的值,返回值0表示key存在不做操作,1表示设置成功。

如果对存在的Key,调用SETNX:

127.0.0.1:6379> SETNX strniuniu fish(integer) 0

对不存在的key,调用SETNX:

127.0.0.1:6379> SETNX strmart fish(integer) 1

3.1.3 DEL

语法:DEL key [key ...]

功能:删除对象,返回值为删除成功了几行。

127.0.0.1:6379> DEL strmart(integer) 1


3.2 读操作

3.2.1 GET

语法:GET key

功能:查询某个key,存在就返回对应的value,如果不存在返回nil。

127.0.0.1:6379> get strniuniu"cat"127.0.0.1:6379> get strmart(nil)

3.2.2 MGET

语法:MGET key [key ...]

功能:一次查询多个key,如果某个key不存在,对应位置返回nil。

127.0.0.1:6379> MGET strniuniu strmart1) "cat"2) (nil)


4.底层实现

4.1 三种编码方式

String看起来简单,但实际有三种编码方式,如下图所示:
INT编码:这个很好理解,就是存一个整型,可以用long表示的整数就以这种编码存储;
EMBSTR编码:如果字符串小于等于阈值字节,使用EMBSTR编码;
RAW编码:字符串大于阈值字节,则用RAW编码。

这个阈值追溯源码的话,是用常量OBJ_ENCODING_EMBSTR_SIZE_LIMIT来表示,3.2版本之前是39字节,3.2版之后是44字节。

// from Redis 7.0.8/* Create a string object with EMBSTR encoding if it is smaller than * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is * used. * * The current limit of 44 is chosen so that the biggest string object * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44robj *createStringObject(const char *ptr, size_t len) {    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)        return createEmbeddedStringObject(ptr,len);    else        return createRawStringObject(ptr,len);}
EMBSTR和RAW都是由redisObject和SDS两个结构组成,它们的差异在于,EMBSTR下redisObject和SDS是连续的内存,RAW编码下redisObject和SDS的内存是分开的。
EMBSTR优点是redisObject和SDS两个结构可以一次性分配空间,缺点在于如果重新分配空间,整体都需要再分配,所以EMBSTR设计为只读,任何操作之后EMBSTR都会变成RAW,理念是发生过修改的字符串通常会认为是易变的。

EMBSTR内存:

RAW内存:

随着我们的操作,编码可能会转换:
INT -> RAW:当存的内容不再是整数,或者大小超过了long的时候;
EMBSTR->RAW:任何操作之后EMBSTR都会变成RAW,原因前面有解释。
我们注意到,字符串编码EMBSTR和RAW都包含一个结构叫SDS,它是Simple Dynamic String的缩写,即简单动态字符串,这是Redis内部作为基石的字符串封装,十分重要,下面我们详细介绍下SDS。

4.2 为什么要SDS?

在C语言中,字符串用一个'\0'结尾的char数组表示。
比如"hello niuniu" 即 "hello niuniu\0" 。
C语言作为一个比较亲和底层的语言,很多基建就是这么简单,但是,对于某个应用来说不一定好用:
1.每次计算字符串长度的复杂度为O(N);
2.对字符串进行追加,需要重新分配内存;
3.非二进制安全。
在Redis内部,字符串的追加和长度计算很常见,这两个简单的操作不应该成为性能的瓶颈,于是Redis封装了一个叫SDS的字符串结构,用来解决上述问题。下面我们来看看它是怎样的结构。

首先,Redis中SDS分为sdshdr8、sdshdr16、sdshdr32、sdshdr64,它们的字段属性都是一样,区别在于应对不同大小的字符串,我们以sdshdr8为例:

// from Redis 7.0.8struct __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[];};

其中有两个关键字段,一个是len,表示使用了多少;一个是alloc,表示一共分配了多少内存。这两个字段的差值(alloc-len)就是预留空间的大小。flags则是标记是哪个分类,比如sdshdr8的分类就是:

#define SDS_TYPE_8  1
从SDS的结构,我们很容易看出SDS是如何对症下药解决问题的:
1.增加长度字段len,快速返回长度;
2.增加空余空间(alloc-len),为后续追加数据留余地;
3.不再以'\0'作为判断标准,二进制安全。
这里可能会想,SDS可以预留空间,那么预留空间有多大呢,规则如下:
len小于1M的情况下,alloc是len的一倍,即预留len大小的空间;
len大于1M的情况下,alloc是1M+len,即预留1M大小的空间。

总结

本节我们学习了String这个Redis基础的对象,String可以存储字符数据、文本数据、二进制数据,虽然String看起来比较简单,但实际上Redis采用了几种编码方式来应对不同的场景,以达到性能的,在后续的学习中,这种的设计也会出现很多次,这也是Redis高性能的原因之一。
下一节,我们将一起分析String的面试点,以及剖析几道容易出现的面试题。

相关文章