Redis-动态字符串

1.动态字符串

redis中的字符串,是对c语言中的字符串(以空字符串结尾的字符数组)进行了一层包装,自己定义了一个结构块,名为动态字符串(simple dynamic string ,SDS)的抽象类型,并且将SDS作为redis默认字符串类型(可以表示字符串、整数、位图)

参考:redis的设计与实现

1.1.动态字符串结构

struct SDS{
    //记录buf数组中已经使用的字节数量
    //等于sds所保存的字符串长度
    int8 len;  // 1byte
    // 记录buf中未使用的字节数量
    int8 free; // 1byte
    //类型标记  int(long 类型整数), embstr 嵌入式字符串(编码后长度小于 44 字节的字符串) , raw(sds 字符串)
    int8 flags;  // 1byte
    //字符数组,用于保存字符串
    byte[] buf[];  // 内联数组,长度为 len
}

1.2.SDS字符串优点

为什么redis性能高,为什么redis要封装字符串.其原因主要在以下几点:

  1. 在常数的获取复杂度上:

    SDS可以直接返回字符串长度,C语言的需要遍历

  2. 缓冲区溢出问题:

    在扩容或者修改时,redis每次会检查free的值,从而直接指导buf是否够用.不够会进行扩容处理.而C语言中的字符串则可能出现由于内存已经分配,修改其中的字符串导致内存溢出问题.

  3. 内存分配问题

    每次C语言处理字符串时,需要重新分配内存,但是redis由于是自定义的结构,相当于可以预支一定的内存.所以可以减少分配次数.在SDS中,如果len小于1MB,则free和len相同,否则redis会每次预支1MB的free

  4. 惰性释放

    redis的惰性释放由于优化redis中字符串的缩短操作.当字符串缩短时,redis不会立马就释放空间,而是使用free记录可利用的空间,便于以后使用.

1.3.字符串常用关命令

命令 描述
set key value 存放一个key-value键值对
get key 根据key获取对应的值
strlen key 获取字符串长度
getrange key index1 index2 获取字符串指定索引范围字符
getset key newValue 获取key的值,并为其设置新的值
mset key1 value1 key2 value2 批量设置key value值
mget key1 key2 批量获取key的值
setnx key value 不存在key 就插入 key value ,返回值 1 成功 0 失败
setrange key index value 找到指定的key,使用value值,从index索引处开始替换
incr key 递增,只对值为数字生效
incrby key 值 指定自增的值
decr key 递减
decrby key 值 指定递减的值
incrbyfloat key 值 指定递增的小数,不推荐可能精度丢失
append key 值 为key的值追加内容
127.0.0.1:6379> set num 123456789
OK
127.0.0.1:6379> get num
"123456789"
127.0.0.1:6379> strlen num
(integer) 9
127.0.0.1:6379> getrange num 0 1
"12"
127.0.0.1:6379> getset num 1234567890
"123456789"
127.0.0.1:6379> get num
"1234567890"
127.0.0.1:6379> mset num1 1 num2 2
OK
127.0.0.1:6379> get num1
"1"
127.0.0.1:6379> mget num1 num2
1) "1"
2) "2"
127.0.0.1:6379> setnx num2 2
(integer) 0
127.0.0.1:6379> setnx num3 3
(integer) 1
127.0.0.1:6379> setrange num 1 000000000
(integer) 10
127.0.0.1:6379> get num
"1000000000"
127.0.0.1:6379> incr num1
(integer) 2
127.0.0.1:6379> incrby num2 10
(integer) 12
127.0.0.1:6379> decr num1
(integer) 1
127.0.0.1:6379> get num1
"1"
127.0.0.1:6379> decrby num2 10
(integer) 2
127.0.0.1:6379> incrbyfloat num1 0.222
"1.222"
127.0.0.1:6379> append num 1
(integer) 11
127.0.0.1:6379> get num
"10000000001"

1.4.字符串类型问题

使用命令 object encoding key可以获取redis中存储的数据的类型.每个数据在redis中都是一个对象.

这个命令的返回值有:

  1. int long整形
  2. embstr 嵌入式字符串(redis 5.x 新增的)
  3. raw redis中的动态字符串

在使用该命令时,会发现一个有意思的问题.

127.0.0.1:6379> set num '1234567890 1234567890 1234567890 1234567890'
OK
127.0.0.1:6379> strlen num
(integer) 43
127.0.0.1:6379> object encoding num
"embstr"
127.0.0.1:6379>set num '1234567890 1234567890 1234567890 1234567890 1'
OK
127.0.0.1:6379> object encoding num
"raw"

注意:当字符串长度为不小于44时,该类型为raw类型

在redis中,每个数据都会当做一个对象处理,而每个对象都会有个头信息.每个对象的头信息一般是==16==个字节

struct RedisObject {
 int4 type; // 4bits     类型
 int4 encoding; // 4bits  
 int24 lru; // 24bits   3字节  LRU 信息
 int32 refcount; // 4bytes   4字节
 void *ptr; // 8bytes,64-bit system   8字节
};

其中:

  1. refcount引用计数,当引用计数为0时,对象就会被销毁,内存会回收.4字节
  2. ptr指针指向对象内容的具体存储位置.8字节

SDS结构体的大小

struct SDS {
 int8 capacity; // 1byte
 int8 len; // 1byte
 int8 flags; // 1byte
 byte[] content; // 内联数组,长度为 capacity
}

SDS的大小是: 1+1+1+?,所以一个SDS的大小最小是3个字节.所以存在redis中一个字符串数据大小,最小16+3个字节,19个字节.

而内存分配器等分内存的大小的单位是2的幂次:2/4/8/16/32/64.为了能容纳一个完成的字符串,那么最少分配32个字节空间.如果字符串稍微大一点就是64个字节空间.如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。

为什么redis会在超过64个字节时当做raw处理呢.或者说为什么字符串长度为44时,就变为了raw呢?

首先,raw是指redis动态字符串,是radis对c语言原生字符串的一种包装.而原生c语言的字符串,最后一个始终使用\0的字符串结尾,是为了方便使用glibc的字符串函数处理,及便于打印输出.而64-19(所有头占用的)=45个字符串.字符串又是以\0结尾,所以embstr 最大能容纳的字符串长度就是 44.

image-20210202204954583.png

你可能感兴趣的:(Redis-动态字符串)