一、String类型简介
String是Redis中最常见的数据存储类型,它存储可以是数字、字符串或二进制数据。由于 Redis 的字符串是二进制安全的,因此可以用来存储图片、视频等二进制数据。其基本编码方式是有OBJ_ENCODING_INT、OBJ_ENCODING_EMBSTR、OBJ_ENCODING_RAW基于简单动态字符串SDS(simple dynamic string)实现,存储上限为512MB。如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时RedisObject与SDS是一段连续空间,申请内存时只需要调用一次内存分配函数,效率更高;当存储的是整型,且能用8个字节进行表示,则Redis会选择使用INT编码来存储,此时redisObject对象中的ptr指针直接替换为long类型。
二、String使用场景
- 对象存储,存储图片、视频等二进制数据;
- 缓存数据,提高访问速度和降低数据库压力。
- 计数器,利用 incr 和 decr 命令实现原子性的加减操作。
- 分布式锁,利用 setnx 命令实现互斥访问。
- 限流,利用 expire 命令实现时间窗口内的访问控制。
三、底层编码方式
在 Redis中String 数据类型的底层实现采用了不同的编码方式,具体取决于存储的值的类型和大小。下面是 Redis中 String 数据类型的三种底层编码方式:
①EMBSTR编码
当存储的值为字符串且长度小于等于 44字节时(旧版本39),Redis 使用EMBSTR编码。在 EMBSTR编码中,此时RedisObject与SDS是一段连续空间,申请内存时只需要调用一次内存分配函数。
②INT编码
当存储的值为整数且值的大小可以用 LONG类型表示时,Redis 使用INT编码。String对象的实际值会被存储ObjectRedis对象ptr指针(8个字节)中,不再需要SDS了。
③RAW编码
当存储的值为字符串长度大于44字节时,Redis使用RAW储存,此时RedisObject与SDS是两端内存,需要分配2次内存,性能较差,其中最大存储上限是512MB。
四、String底层数据结构
Redis的String类型的实际储存结构都是简单动态字符串SDS(Simple Dynamic Strings),OBJ_ENCODING_INT、OBJ_ENCODING_EMBSTR、OBJ_ENCODING_RAW三种编码方式只是负责储存元数据(字符长度、SDS指针等)。
SDS包含3种编码类型
- OBJ_ENCODING_EMBSTR:占用64Bytes的空间,存储44Bytes的数据
- OBJ_ENCODING_RAW:存储大于44Bytes的数据
- OBJ_ENCODING_INT:存储整数类型
Redis 6.0版本相比Redis 5.0版本在SDS底层数据结构上进行了一些改进和优化。Redis 6.0中的SDS仍然包含三个成员变量len、free和buf,但是buf不再是一个字符数组,而是一个unsigned char类型的数组。此外,在Redis 6.0中新增了四个SDS类:sdsHdr5、sdsHdr8、sdsHdr16和sdsHdr32。这四个类分别代表SDS字符串的头部数据结构,用于存储SDS字符串的长度和空闲空间,以及标记SDS字符串的类型。
Redis 6.0版本中的这些改进和优化可以提高SDS的效率和灵活性。通过使用unsigned char类型的数组来存储SDS字符串,可以更好地处理二进制数据和字符编码。而新增的四个SDS类可以更灵活地处理不同长度的SDS字符串,减少内存浪费。此外,为了提高效率,Redis 6.0版本中还对SDS的内存管理进行了优化,避免了频繁的内存分配和释放操作。
①SDS类对象
typedef struct sdshdr {
//buf已保存的字符串字节数,不包含结束标示
uint32_t len;
//buf申请的总的字节数,不包含结束标示
uint32_t alloc;
//不同SDS的头类型,用来控制SDS的头大小
unsigned char flags;
//用于存储字符串数据
char buf[];
}sds;
- SDS_TYPE_5:这个比较特殊,注释上说未被使用。
- SDS_TYPE_8: 当len 小于 1 << 8,也就是小于256时,变量len和alloc用uint8类型。
- SDS_TYPE_16: 当len 小于 1 << 16,也就是小于65536时,变量len和alloc用uint16类型。
- SDS_TYPE_32: 当len 小于 1 << 32,也就是小于4294967296时,变量len和alloc用uint32类型。
- SDS_TYPE_64: 当len 大于等于 1 << 32,也就是大于等于4294967296时,变量len和alloc用uint64类型。
②EMBSTR底层数据结构
当String的长度小于等于44字节时(旧版本39),Redis使用EMBSTR储存,此时RedisObject与SDS是一段连续空间,申请内存时只需要调用一次内存分配函数。
QA:为啥Redis底层使用44字节作为限制编码方式呢?
对于RedisObject包装的统一对象结构大小是16字节,SDS对象头是3字节,字符串大小是44字节,对象尾是1字节,加起来是64字节,而Redis默认使用jemalloc而不是glibc的malloc内存分配算法,这种算法是2的幂次方去分配内存的,所以44字节正好符合Redis内存分配大小,避免了内存碎片的产生。
③INT底层数据结构
当String存储的字符串是整数值,并且大小在LONG MAX范围内,则会采用INT编码: 直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。
④RAW底层数据结构
当String的长度大于44字节时,Redis使用RAW储存,此时RedisObject与SDS是两端内存,需要分配2次内存,性能较差,其中最大存储上限是512MB。
注:一旦转为RAW,就算进行删减操作使字节小于44字节,也不会再转回EMBSTR。
五、String常用命令
①SET key value:设置指定 key 的值,如果key的值已存在就覆盖旧值。
语法:
localhost:0>set name ‘张三’
"OK"
可用版本: >= 1.0.0
返回值: 在 Redis 2.6.12 以前版本, SET 命令总是返回 OK 。
②GET key:获取指定key的指,如果key不存在返回null。
语法:
localhost:0>get name1
null
localhost:0>get name
"张三"
可用版本: >= 1.0.0
返回值: 返回 key 的值,如果 key 不存在时,返回 nil。 如果 key 不是字符串类型,那么返回一个错误。
③GETRANGE key start end:获取指定key中字符串值的子字符。
语法:
localhost:0>set string 'set redis string type value is null'
"OK"
localhost:0> GETRANGE string 0 2
"'set"
localhost:0> GETRANGE string 0 -1
"set redis string type value is null"
可用版本: >= 2.4.0
返回值: 截取得到的子字符串。
④GETSET key value,设置指定 key 的值设为 value ,并返回 key 的旧值(old value)。
语法:
localhost:0>getset name '李四'
"张三"
localhost:0>getset name1 'lili'
null
可用版本: >= 1.0.0
返回值: 返回给定 key 的旧值。 当 key 没有旧值时,即 key 不存在时返回 null 。
⑤GETBIT key offset:对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
语法:
# 对不存在的 key 或者不存在的 offset 进行 GETBIT, 返回 0
localhost:0>get name2
null
localhost:0>getbit name2 1
"0"
localhost:0>getbit name2 1024
"0"
localhost:0>getbit name 2
"1"
localhost:0>getbit name 1024
"0"
可用版本: >= 2.2.0
⑥MGET key1 [key2..] 获取所有(一个或多个)指定 key 的值。
语法:
localhost:0>mget name name1
1) "李四"
2) "lili"
localhost:0>mget name name1 name2
1) "李四"
2) "lili"
3) null
可用版本: >= 1.0.0
返回值: 一个包含所有给定 key 的值的列表,如果某个 key 不存在,那么这个 key 返回 null
⑦ SETBIT key offset value:对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
语法:
localhost:0>getbit name1 1
"1"
localhost:0>setbit name1 1 0
"1"
localhost:0>getbit name1 1
"0"
可用版本: >= 2.2.0
⑧ SETEX key seconds value:将值 value 关联到指定 key ,并将 key 的过期时间设为 seconds (以秒为单位),如果 key 已经存在, SETEX 命令将会替换旧的值。
localhost:0>setex name2 10 '赵五'
"OK"
可用版本: >= 2.0.0
⑨ SETNX key value :只有指定的key 不存在时,才能设置 key 的值。
语法:
localhost:0>setnx name3 '孙六'
"1"
localhost:0>setnx name2 '孙六'
"0"
可用版本: >= 1.0.0
⑩ SETRANGE key offset value :用 value 参数覆写指定定 key 所储存的字符串值,从偏移量 offset 开始。
语法:
localhost:0>get string
"set redis string type value is null"
localhost:0>setrange string 10 "List type value is [linkedlist]"
"41"
localhost:0>get string
"set redis List type value is [linkedlist]"
可用版本: >= 2.2.0
⑪ STRLEN key:返回指定 key 所储存的字符串值的长度。
语法:
localhost:0>strlen string
"41"
localhost:0>strlen string1
"0"
可用版本: >= 2.2.0
返回值: 字符串值的长度。 当 key 不存在时,返回 0。
⑫ MSET key value [key value ...]:同时设置一个或多个 key-value 对,对于已经存在的key,覆盖旧值。
语法:
localhost:0>set key1 goodbye
"OK"
localhost:0>mset key1 hello key2 world
"OK"
localhost:0>get key1
"hello"
localhost:0>get key2
"world"
可用版本: >= 1.0.1
⑬ MSETNX key value [key value ...]:同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
语法:
localhost:0>msetnx key2 reset key3 msetnx key4 value
"0"
localhost:0>msetnx key3 msetnx key4 value
"1"
可用版本: >= 1.0.1
返回值: 当所有 key 都成功设置,返回 1 。 如果所有指定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。
⑭ PSETEX key milliseconds value:该命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
语法:
localhost:0>PSETEX key4 1000 hello
"OK"
localhost:0>get key4
null
可用版本: >= 2.6.0
⑮ INCR key:将 key 中储存的数字值增一,数值限制在 64 位(bit)有符号数字表示之内。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
语法:
localhost:0>incr key3
"ERR value is not an integer or out of range"
localhost:0>get key4
null
localhost:0>incr key4
"1"
localhost:0>get key4
"1"
localhost:0>incr key4
"2"
返回值: 执行 INCR 命令之后 key 的值。
⑯ INCRBY key increment:指定的key 所储存的值加上给定的增量值(increment),数值限制在 64 位(bit)有符号数字表示之内。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 操作 。
语法:
localhost:0>incrby key3 2
"ERR value is not an integer or out of range"
localhost:0>get key4
"2"
localhost:0>incrby key4 3
"5"
localhost:0>incrby key5 3
"3"
localhost:0>get key4
"5"
localhost:0>get key5
"3"
可用版本: >= 1.0.0
⑰ INCRBYFLOAT key increment:将指定的key 所储存的值加上给定的浮点增量值(increment),如果 key 不存在,那么 INCRBYFLOAT 会先将 key 的值设为 0 ,如果key非数值类型,执行会报错。
语法:
localhost:0>get key5
"3"
localhost:0>incrbyfloat key5 0.1313
"3.1313"
localhost:0>get key6
null
localhost:0>incrbyfloat key6 3.1415
"3.1415"
localhost:0>set key6 hello
"OK"
localhost:0>incrbyfloat key6 3.1415
"ERR value is not a valid float"
可用版本: >= 2.6.0
⑱ DECR key将 key 中储存的数字值减一,该命令数值限制在 64 位(bit)有符号数字表示之内。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。
语法:
localhost:0>get key7
null
localhost:0>decr key7
"-1"
localhost:0>set key7 hello
"OK"
localhost:0>decr key7
"ERR value is not an integer or out of range"
返回值: 执行命令之后 key 的值。
⑲ DECRBY key decrement:指定key 所储存的值减去给定的减量值(decrement),所操作的数值限制在 64 位(bit)有符号数字表示之内。如果值包含错误的类型或字符串类型的值不能表示为数字,那么返回一个错误。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECRBY 操作。
语法:
localhost:0>get key4
"5"
localhost:0>decrby key4 2
"3"
localhost:0>get key5
"3.1313"
localhost:0>decrby key5 2
"ERR value is not an integer or out of range"
localhost:0>get key6
"hello"
localhost:0>decrby key6 2
"ERR value is not an integer or out of range"
可用版本: >= 1.0.0
⑳ APPEND key value:指定的 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
语法:
localhost:0>get key5
"3.1313"
localhost:0>append key5 ':圆周率'
"18"
localhost:0>get key5
"3.1313:圆周率"
localhost:0>get key6
"hello"
localhost:0>append key6 world
"10"
localhost:0>get key6
"helloworld"
localhost:0>get key7
null
localhost:0>append key7 hello
"5"
localhost:0>get key7
"hello"
可用版本: >= 2.0.0