一、数据类型介绍

字符串类型是 Redis 最基础的数据结构。Redis 中的键都是字符串类型,而且其他几种数据结构都是在字符串类型的基础上构建的。字符串通常用于缓存,但还支持额外的功能,能够实现计数器并执行按位操作等。

Redis 字符串用于存储字节序列,包括文本、序列化对象和二进制数组。它的值可以是简单的字符串,复杂的字符串(JSON、XML),数字(整数、浮点数),甚至是二进制数据(图片、音频、视频),但是值最大不能超过 512 MB

因为 Redis 键是字符串,当我们也将字符串类型用作值时,我们实际上是将一个字符串映射到另一个字符串。字符串数据类型在许多用例中非常有用,比如缓存 HTML 片段或页面。

使用 SETGET 命令可以设置和检索字符串值。注意,SET 会替换已经存在的值,即使该键关联的是非字符串值,因为 SET 执行的是赋值操作。

SET 命令有一些选项,可以作为附加参数提供。例如,可以要求 SET 在键已存在时失败,或者仅在键已存在时成功:

1
2
3
4
$ set bike:1 bike nx
(nil)
$ set bike:1 bike xx
OK

还有许多其他命令可以对字符串进行操作。例如,GETSET 命令将键的值设置为新值,并返回旧值作为结果。你可以在网站每次接收到新访客时使用 INCR 来增加 Redis 键的值。你可能想每小时收集一次该信息,而不丢失任何增量。你可以 GETSET 键,给它赋值 “0”,并读取旧值。

INCR 命令将字符串值解析为整数,将其增加 1,然后将获得的值设置为新的值。还有其他类似的命令,如 INCRBYDECRDECRBY。内部实现上,它们始终是同一个命令,只是行为略有不同。

INCR 是原子性的意味着即使多个客户端针对同一个键执行 INCR 操作,也不会发生竞争条件。例如,永远不会出现客户端一读取到 “10”,客户端二同时也读取到 “10”,两者都将其增加到 11,然后将新值设置为 11 的情况。最终值将始终是 12,并且读取-增加-赋值操作是在其他客户端没有同时执行命令的情况下完成的。

另外还有 MSETMGET 命令,用于一次设置或检索多个键的值:

1
2
3
$ mset {string} "value" {string}2 "value2"

$ mget {string} {string}2

获取和赋值操作

  • SET,存储一个字符串值。
  • SETNX,仅在键不存在时存储一个字符串值。适用于实现锁。
  • GET,检索一个字符串值。
  • MGET,在一次操作中检索多个字符串值。

计数器

  • INCR,原子性地将存储在指定键处的计数器增加 1。
  • INCRBY,原子性地将存储在指定键处的计数器增加(传递负数时减少)。
  • 还有一个用于浮点数计数器的命令:INCRBYFLOAT

性能表现:大多数字符串操作的时间复杂度为 O(1),这意味着它们非常高效。然而,需要注意 SUBSTRGETRANGESETRANGE 命令,这些命令的时间复杂度为 O(n)。当处理大字符串时,这些随机访问字符串命令可能会导致性能问题。

替代方案:如果要将结构化数据作为序列化字符串存储,也可以考虑使用 Redis 中提供的 Hash 表或 JSON。

二、命令一览

命令 作用
APPEND
DECR
DECRBY
GET
GETDEL
GETEX
GETRANGE
GETSET
INCR
INCRBY
INCRBYFLOAT
LCS
MAGE
MSET 将给定的键设置为相应的值
MSETNX
PSETEX
SET 将键设置为某字符串值
SETEX
SETNX
SETRANGE
STRLEN
SUBSTR

三、命令详情

1、SET

1
2
SET key value [NX | XX] [GET] [EX seconds | PX milliseconds |
EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

时间复杂度 O(1)

将键设置为某字符串值。如果键已经持有某个值,无论其类型如何,该值将被覆盖。成功的 SET 操作会丢弃与键关联的任何之前的过期时间。

SET 命令支持一组修改其行为的选项:

  • EX seconds,设置指定的过期时间,以秒为单位(一个正整数)。
  • PX milliseconds,设置指定的过期时间,以毫秒为单位(一个正整数)。
  • EXAT timestamp-seconds,设置键将在指定的 Unix 时间(以秒为单位,一个正整数)过期。
  • PXAT timestamp-milliseconds,设置键将在指定的 Unix 时间(以毫秒为单位,一个正整数)过期。
  • NX,仅在键不存在时设置键。
  • XX,仅在键已存在时设置键。
  • KEEPTTL,保留与键关联的过期时间。如果键已有过期时间,更新值但是需要保留过期时间时使用此选项。
  • GET,返回键之前存储的旧字符串,如果键不存在则返回 nil。如果键存储的值不是字符串,则返回错误并且 SET 操作终止。

注意

由于 SET 命令的选项可以替代 SETNXSETEXPSETEXGETSET,在未来版本的 Redis 中,这些命令可能会被废弃并最终移除。

1.1 范例

注意:下面的模式不被推荐,而是建议使用稍微复杂一点但提供更好保证和容错能力的 Redlock 算法。

命令 SET resource-name anystring NX EX max-lock-time 是用 Redis 实现分布式锁的一种简单方法。

如果上述命令返回 OK,则客户端可以获取锁(如果命令返回 Nil,则在一段时间后重试),并且只需使用 DEL 移除锁。

当达到过期时间时,锁将自动释放。

可以通过修改解锁方案使这个系统更加健壮:

  • 不设置固定字符串,而是设置一个不可猜测的大随机字符串,称为 token。
  • 不是通过 DEL 释放锁,而是发送一个脚本,只有在值匹配时才删除键。

这避免了一个客户端会在过期时间之后尝试释放锁,从而删除了另一个稍后获取锁的客户端创建的键

解锁脚本的例子类似于以下内容:

1
2
3
4
5
6
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end

该脚本应使用 EVAL ...script... 1 resource-name token-value 调用。

2、MSET

1
MSET key value [key value ...]

时间复杂度 O(N),其中 N 是设置的键的数量。

将给定的键设置为相应的值。MSET 会用新值替换已存在的值,就像普通的 SET 命令一样。如果不想覆盖已有的值需要使用 MSETNX

MSET 是原子性的,因此所有给定的键都会同时被设置。客户端不可能看到部分键已被更新而其他键保持不变的情况。

1
2
3
4
5
6
redis> MSET key1 "Hello" key2 "World"
"OK"
redis> GET key1
"Hello"
redis> GET key2
"World"

3、自增自减命令

  • INCR
  • DECR
  • INCRBY
  • DECRBY
  • INCRBYFLOAT

很多存储系统和编程语言内部使用 CAS 机制实现计数功能,会有一定的 CPU 开销,但在 Redis 中完全不存在这个问题,因为 Redis 是单线程架构,任何命令到了 Redis 服务端都要顺序执行。

三、其他

1、关于批量操作

批量操作命令可以有效地提高开发效率,将 mget 分解为 n 次 get 命令,执行耗时为 n次get时间 = n次网络时间 + n次命令时间,而使用 mget 命令后,执行耗时为 n次get时间 = 1次网络时间 + n次命令时间

Redis 可以支撑每秒数万的读写操作,但是这指的是 Redis 服务端的处理能力,对于客户端来说,一次命令除了命令时间还有网络时间。Redis 的处理能力已经足够高,对于开发者来说,网络可能会成为性能的平静。

另外,批量操作在集群模式下需要注意,集群模式使用虚拟槽分区,虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为。Redis Cluster 槽范围为 0 到 16383(2 的 14 次幂)。例如集群有 5 个节点,每个节点平均大约负责 3276 个槽。所有的键会根据哈希函数映射到 0 到 16383 整数槽内,计算公式: slot=CRC16(key)&16383,所以使用 mget 批量获取键的时候需要注意,msetmget 目前只支持具有相同 slot 值的 key 执行批量操作。对于映射为不同 slot 值的 key 由于执行 msetmget 等操作可能存在于多个节点上因此不被支持。

示例如下:

1
2
mset string "value" string2 "value2"
mget string string2

上面两个命令在使用时都会有如下错误,当尝试在一个命令中使用多个键,但这些键分布在集群的不同槽中时,就会出现这个错误。因为 Redis 集群要求在单个命令中使用的所有键必须位于同一个槽中,以确保操作的原子性和一致性。

1
CROSSSLOT Keys in request don't hash to the same slot

解决这个问题,可以使用哈希标签(hash_tag)。键命令执行分为两步:计算槽,查找槽锁对应的节点。在计算槽时,如果键的内容包含大括号字符 {},则计算槽的有效部分是大括号中的内容,否则采用键的全部内容来计算槽。键内部大括号包含的内容叫做 hash_tag,它提供不同的键可以具备相同 slot 的功能,常用于 Redis IO 优化。

解决后如下:

1
2
3
4
mset {string} "value" {string}2 "value2"
mget {string} {string}2
"value"
"value2"

但是,使用哈希标签,又会引入集群倾斜问题。当不同的槽对应键数量差异过大时,会引发集群数据倾斜问题。当大量使用 hash_tag 时,会产生不同的键映射到同一个槽的情况。特别是选择作为 hash_tag 的数据离散度较差时,将加速槽内键数量倾斜情况。

2、时间复杂度

3、字符串的内部编码

字符串类型的内部编码有如下 3 种:

  • int,8 字节长整型
  • embstr,小于等于 39 个字节的字符串
  • raw,大于 39 个字节的字符串

Redis 会根据当前值的类型和长度决定使用哪种内部编码实现。

整数类型示例如下:

1
2
3
set intkey 2222  
object encoding intkey
"int"

短字符串示例如下:

1
2
3
4
5
set stringkey stringvalue  
object encoding stringkey
"embstr"
strlen stringkey
11

长字符串示例如下:

1
2
3
4
5
set rawstringkey abcdefghijklmnopqrstuvwxyz-zyxwvutsrqponmlkjihgfedcba  
object encoding rawstringkey
"raw"
strlen rawstringkey
53

相关链接

Redis Strings | Docs

OB tags

#Redis #未完待续