有道无术,术尚可求,有术无道,止于术。
本系列Redis 版本 7.2.5
源码地址:https://gitee.com/pearl-organization/study-redis-demo
文章目录
- 1. 前言
- 2. 常用命令
- 2.1 SET
- 2.2 GET
- 2.3 MSET
- 2.4 MGET
- 2.5 GETSET
- 2.6 STRLEN
- 2.7 SETEX
- 2.8 SETNX
- 2.9 INCR
- 2.10 DECR
- 3. SDS(简单动态字符串)
- 3.1 二进制安全
- 3.2 查询字符串长度效率高
- 3.3 缓冲区溢出保护
- 3.4 空间预分配
- 3.5 惰性空间释放
- 4. 应用场景
- 4.1 存储常规数据
- 4.2 计数器
- 4.3 分布式锁
1. 前言
在官方文档中,可以看到 Redis
支持很多种数据类型,以解决不同场景下的各种问题。接下来,会详细介绍 Redis
中的各种数据类型, 主要是介绍一些常用的,包括:
String
List
Hash
Set
Sorted set
(ZSet
)Bitmap
HyperLogLog
Geospatial
Stream
String
字符串是编程语言里最常用的数据类型,使用双引号表示。
比如JAVA
:
String str="1234";
比如C
语言:
char str[] = "1234";
在 Redis
中, String
类型是最基本的数据类型,是二进制安全的,可以存储任意类型的数据,文本、数字、图片或者序列化的对象 。
注意:Key
的类型只能为字符串,Value
的类型为字符串时,默认情况下的最大值是 512M
。
2. 常用命令
String
相关的所有命令:
命名 | 描述 |
---|---|
APPEND | 将 value 追加到 key 原来的值的末尾 |
DECR | 将 key 中储存的数字值减一 |
DECRBY | 将 key 所储存的值减去给定的减量值 ( decrement ) |
GET | 设置指定 key 的值 |
GETDEL | 获取 key 的值并删除该 key |
GETEX | 获取 key 的值,并可选择设置其过期时间 |
GETRANGE | 返回 key 中字符串值的子字符 |
GETSET | 将给定 key 的值设为 value ,并返回 key 的旧值 |
INCR | 将 key 中储存的数字值增一 |
INCRBY | 将 key 所储存的值加上给定的增量值 ( increment ) |
INCRBYFLOAT | 将 key 所储存的值加上给定的浮点增量值 ( increment ) |
LCS | 实现了最长公共子序列算法,可用于评估字符串的相似程度 |
MGET | 获取所有(一个或多个)给定 key 的值 |
MSET | 同时设置一个或多个 key-value 对 |
MSETNX | 同时设置一个或多个 key-value 对 |
PSETEX | 以毫秒为单位设置 key 的生存时间 |
SET | 设置指定 key 的值 |
SETEX | 设置 key 的值为 value 同时将过期时间设为 seconds |
SETNX | 只有在 key 不存在时设置 key 的值 |
SETRANGE | 从偏移量 offset 开始用 value 覆写给定 key 所储存的字符串值 |
STRLEN | 返回 key 所储存的字符串值的长度 |
SUBSTR | 返回字符串值的子字符串,由偏移量开始和结束(两者都包含在内)决定 |
2.1 SET
用于给指定的 Key
设置字符串值,如果 Key
已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。当 SET
命令执行成功后,之前设置的过期时间都将失效。
示例:
127.0.0.1:6379> SET mykey "Hello World"
OK
127.0.0.1:6379> SET mykey "Hello World" EX 60
OK
2.2 GET
用于获取指定 Key
的字符串值,如果 Key
不存在, 那么返回特殊值 nil
, 如果键 Key
的值不是字符串类型, 返回错误, 因为 GET
命令只能用于字符串值。
示例:
127.0.0.1:6379> GET mykey
"Hello World"
127.0.0.1:6379> GET mykey_nil
(nil)
2.3 MSET
和 SET
命令一样,会用新值替换旧值, MSET
是原子操作,所有 Key
的值同时设置,客户端不会看到有些 Key
值被修改,而另一些 Key
值没变。总是返回 OK
,因为 MSET
不会失败。
基本语法:
MSET key1 value1 key2 value2 .. keyN valueN
示例:
127.0.0.1:6379> MSET key1 "Hello" key2 "World"
OK
127.0.0.1:6379> GET key1
"Hello"
127.0.0.1:6379> GET key2
"World"
2.4 MGET
用于获取所有给定 Key
的值,返回一个列表,值的类型是字符串,如果某个 Key
不存在或者值不是字符串,那么这个 Key
返回特殊值 nil
。
基本语法:
MGET KEY1 KEY2 .. KEYN
示例:
127.0.0.1:6379> MGET key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)
2.5 GETSET
用于给指定的 Key
设置字符串值, 并返回设置之前的旧值。如果 Key
没有旧值返回 nil
,如果 Key
对应的值存在但不是字符串类型时,返回一个错误。
示例:
127.0.0.1:6379> SET mykey "Hello"
OK
127.0.0.1:6379> GETSET mykey "World"
"Hello"
127.0.0.1:6379> GET mykey
"World"
2.6 STRLEN
用于获取指定 Key
所储存的字符串值的长度,当储存的不是字符串类型时,返回错误。
基本语法:
STRLEN KEY_NAME
示例:
127.0.0.1:6379> SET mykey "Hello world"
OK
127.0.0.1:6379> STRLEN mykey
(integer) 11
127.0.0.1:6379> STRLEN mykey_nil
(integer) 0
2.7 SETEX
用于给指定的 Key
设置字符串值,并将 Key
的生存时间设置为多少秒。如果键 Key
已经存在,将覆盖已有的值。
基本语法:
SETEX key seconds value
示例:
127.0.0.1:6379> SETEX mykey 60 "Hello"
OK
127.0.0.1:6379> TTL mykey
(integer) 52
127.0.0.1:6379> GET mykey
"Hello"
2.8 SETNX
SETNX
是 SET if Not eXists
的缩写,在指定的 Key
不存在时,设置指定的值,这种情况下等同 SET
命令,当 Key
存在时,什么也不做。
返回值为整数:
1
:Key
存在0
:Key
不存在
SETNX
命令是原子性的,常用于实现分布式锁,添加成功表示获取到锁,添加失败表示未获取到锁(后续详解介绍)。
示例:
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
2.9 INCR
用于将指定的 Key
储存的数字值增一,如果 Key
不存在,那么值会先被初始化为 0
,然后再执行 INCR
操作,返回值为执行操作之后 Key
的值。如果值包含错误的类型,或字符串类型的值不能表示为数字,将会返回一个错误 ERR ERR hash value is not an integer
。
操作的值限制在 64
位(bit
)有符号数字表示之内。本质上这是一个字符串操作,因为 Redis
没有专门的整数类型,储在 Key
中的字符串被转换为十进制有符号整数,在此基础上加 1
。常用于计数器、限流(后续详解介绍)。
基本语法:
INCR KEY_NAME
示例:
127.0.0.1:6379> SET mykey "10"
"OK"
127.0.0.1:6379> INCR mykey
(integer) 11
127.0.0.1:6379> GET mykey
"11"
2.10 DECR
用于将指定的 Key
储存的数字值减去一,如果 Key
不存在,那么值会先被初始化为 0
,然后再执行 DECR
操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,将会返回一个错误。操作的值限制在 64
位(bit
)有符号数字表示之内,返回值为执行操作之后 Key
的值
基本语法:
DECR KEY_NAME
示例:
127.0.0.1:6379> DECR mykey
(integer) 9
127.0.0.1:6379> SET mykey "234293482390480948029348230948"
"OK"
127.0.0.1:6379>DECR mykey
ERR ERR value is not an integer or out of range
3. SDS(简单动态字符串)
SDS
是 Simple Dynamic String
的首字母缩写,翻译为简单的动态字符串,是 Redis
自定义的一种表示字符串的特殊数据结构。相较于传统的C
语言字符串,SDS
具有更多的功能和更高的性能,当前也会占用更多的内存。
Redis 3.2
之前的版本:
struct sdshdr {
int len; // 记录buf数组中已使用字节的数量,即字符串的实际长度
int free; // 存储剩余(空闲)的空间
char buf[]; // 存储实际数据
};
Redis 3.2
之后的版本,SDS
变成了 5
种数据结构,不同类型的存储空间大小不一样:
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 已用空间的长度,占 4 个字节,不包括 ‘\0’;
uint8_t alloc; // 实际分配长度,占 4 个字节,不包括 ‘\0’;
unsigned char flags; // 标记当前 buf[] 类型( sdshdr8/16/32/64)
char buf[]; // 存储实际数据
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
相比于C
语言字符串,SDS
有以下优点:
- 二进制安全
- 查询字符串长度效率高
- 缓冲区溢出保护
- 空间预分配
- 惰性空间释放
3.1 二进制安全
在 C
语言中,用字符 \0
表示字符串的结束,如果字符串中本身就有 \0
字符,字符串就会被截断,即非二进制安全,例如 hello \0 world
会被截断解析为 hello
。
SDS
不受 \0
字符影响,能够准确存储任意二进制数据,因为它基于 len
属性而非遇到 \0
来判断字符串的结束位置。
3.2 查询字符串长度效率高
在 C
语言中,获取一个字符串长度时,需对整个字符串进行遍历,直至遇到结束符号。在高并发场景下频繁遍历字符串,势必会造成性能瓶颈。
SDS
中于 len
属性记录了字符串的长度,可以直接通过 STRLEN
命令获取长度即可。
3.3 缓冲区溢出保护
在 C
语言中,字符串本身不记录可用空间大小,在进行字符串拼接等操作时,如果没有检查就可能会导致缓冲区溢出。
SDS
中于 free
属性记录了额外未使用的空间大小,每次进行字符串操作前都会检查剩余空间是否足够,不足时会自动扩容,有效避免了缓冲区溢出问题。
3.4 空间预分配
在 C
语言中,每次修改字符串长度(增加或减少)时,通常需要重新分配内存,这可能导致频繁的内存操作。
SDS
优化了字符串增长操作,当修改字符串并需对 SDS
的空间进行扩展时,不仅会为 SDS
分配修改所必要的空间,还会分配额外的未使用空间(free
属性),下次再修改就先检查未使用空间free
是否满足,满足则不用在扩展空间。有效的减少字符串连续增长操作,减少所产生的内存重分配次数。
3.5 惰性空间释放
惰性空间释放策略则用于优化 SDS
字符串缩短操作,当缩短 SDS
字符串后,并不会立即执行内存重分配来回收多余的空间,而是用 free
属性将这些空间记录下来,如果后续有增长操作,则可直接使用。
4. 应用场景
4.1 存储常规数据
String
可以存储任意类型的数据,文本、数字、图片或者序列化的对象 。
常用实际场景:
- 图形、短信验证码
- 令牌
例如,存储短信验证码并设置过期时间为 60
秒:
localhost:0>SET 13688889999 80906 EX 60
"OK"
localhost:0>GET 13688889999
"80906"
4.2 计数器
INCR
用于将指定的 Key
储存的数字值增一, DECR
用于将指定的 Key
储存的数字值减去一。
常用实际场景:
- 阅读数
- 点赞
- 分布式
ID
- 网站访问量
- 记录接口访问次数,实现防止重复提交、简单限流
例如,使用 INCR
记录网站每日访问量:
localhost:0>SET web_views:20240617 0
"OK"
localhost:0>INCR web_views:20240617
"1"
localhost:0>GET web_views:20240617
"1"
4.3 分布式锁
SETNX
命令是原子性的,常用于实现分布式锁,添加成功表示获取到锁,添加失败表示未获取到锁(后续详解介绍)。
例如,执行 SETNX
添加锁,返回 1
表示成功获取到锁:
localhost:0>SETNX my_lock yes
"1"
其他线程执行SETNX
,返回 0
表示获取锁失败:
localhost:0>SETNX my_lock yes
"0"
执行完成后,删除锁,其他线程再次获取则成功:
localhost:0>DEL my_lock
"1"
localhost:0>SETNX my_lock yes
"1"