Redis 中的 String 数据类型
一、数据类型介绍
字符串类型是 Redis 最基础的数据结构。Redis 中的键都是字符串类型,而且其他几种数据结构都是在字符串类型的基础上构建的。字符串通常用于缓存,但还支持额外的功能,能够实现计数器并执行按位操作等。
Redis 字符串用于存储字节序列,包括文本、序列化对象和二进制数组。它的值可以是简单的字符串,复杂的字符串(JSON、XML),数字(整数、浮点数),甚至是二进制数据(图片、音频、视频),但是值最大不能超过 512 MB。
因为 Redis 键是字符串,当我们也将字符串类型用作值时,我们实际上是将一个字符串映射到另一个字符串。字符串数据类型在许多用例中非常有用,比如缓存 HTML 片段或页面。
使用 SET
和 GET
命令可以设置和检索字符串值。注意,SET
会替换已经存在的值,即使该键关联的是非字符串值,因为 SET
执行的是赋值操作。
SET
命令有一些选项,可以作为附加参数提供。例如,可以要求 SET
在键已存在时失败,或者仅在键已存在时成功:
1 | set bike:1 bike nx |
还有许多其他命令可以对字符串进行操作。例如,GETSET
命令将键的值设置为新值,并返回旧值作为结果。你可以在网站每次接收到新访客时使用 INCR
来增加 Redis 键的值。你可能想每小时收集一次该信息,而不丢失任何增量。你可以 GETSET
键,给它赋值 “0”,并读取旧值。
INCR
命令将字符串值解析为整数,将其增加 1,然后将获得的值设置为新的值。还有其他类似的命令,如 INCRBY
、DECR
和 DECRBY
。内部实现上,它们始终是同一个命令,只是行为略有不同。
INCR
是原子性的意味着即使多个客户端针对同一个键执行 INCR
操作,也不会发生竞争条件。例如,永远不会出现客户端一读取到 “10”,客户端二同时也读取到 “10”,两者都将其增加到 11,然后将新值设置为 11 的情况。最终值将始终是 12,并且读取-增加-赋值操作是在其他客户端没有同时执行命令的情况下完成的。
另外还有 MSET
和 MGET
命令,用于一次设置或检索多个键的值:
1 | mset {string} "value" {string}2 "value2" |
获取和赋值操作:
SET
,存储一个字符串值。SETNX
,仅在键不存在时存储一个字符串值。适用于实现锁。GET
,检索一个字符串值。MGET
,在一次操作中检索多个字符串值。
计数器:
INCR
,原子性地将存储在指定键处的计数器增加 1。INCRBY
,原子性地将存储在指定键处的计数器增加(传递负数时减少)。- 还有一个用于浮点数计数器的命令:
INCRBYFLOAT
。
性能表现:大多数字符串操作的时间复杂度为 O(1)
,这意味着它们非常高效。然而,需要注意 SUBSTR
、GETRANGE
和 SETRANGE
命令,这些命令的时间复杂度为 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 | SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | |
时间复杂度 O(1)
。
将键设置为某字符串值。如果键已经持有某个值,无论其类型如何,该值将被覆盖。成功的 SET
操作会丢弃与键关联的任何之前的过期时间。
SET
命令支持一组修改其行为的选项:
EX seconds
,设置指定的过期时间,以秒为单位(一个正整数)。PX milliseconds
,设置指定的过期时间,以毫秒为单位(一个正整数)。EXAT timestamp-seconds
,设置键将在指定的 Unix 时间(以秒为单位,一个正整数)过期。PXAT timestamp-milliseconds
,设置键将在指定的 Unix 时间(以毫秒为单位,一个正整数)过期。NX
,仅在键不存在时设置键。XX
,仅在键已存在时设置键。KEEPTTL
,保留与键关联的过期时间。如果键已有过期时间,更新值但是需要保留过期时间时使用此选项。GET
,返回键之前存储的旧字符串,如果键不存在则返回nil
。如果键存储的值不是字符串,则返回错误并且 SET 操作终止。
注意
由于 SET
命令的选项可以替代 SETNX
、SETEX
、PSETEX
、GETSET
,在未来版本的 Redis 中,这些命令可能会被废弃并最终移除。
1.1 范例
注意:下面的模式不被推荐,而是建议使用稍微复杂一点但提供更好保证和容错能力的 Redlock 算法。
命令 SET resource-name anystring NX EX max-lock-time
是用 Redis 实现分布式锁的一种简单方法。
如果上述命令返回 OK,则客户端可以获取锁(如果命令返回 Nil
,则在一段时间后重试),并且只需使用 DEL
移除锁。
当达到过期时间时,锁将自动释放。
可以通过修改解锁方案使这个系统更加健壮:
- 不设置固定字符串,而是设置一个不可猜测的大随机字符串,称为 token。
- 不是通过 DEL 释放锁,而是发送一个脚本,只有在值匹配时才删除键。
这避免了一个客户端会在过期时间之后尝试释放锁,从而删除了另一个稍后获取锁的客户端创建的键。
解锁脚本的例子类似于以下内容:
1 | if redis.call("get", KEYS[1]) == ARGV[1] |
该脚本应使用 EVAL ...script... 1 resource-name token-value
调用。
2、MSET
1 | MSET key value [key value ...] |
时间复杂度 O(N)
,其中 N 是设置的键的数量。
将给定的键设置为相应的值。MSET
会用新值替换已存在的值,就像普通的 SET
命令一样。如果不想覆盖已有的值需要使用 MSETNX
。
MSET
是原子性的,因此所有给定的键都会同时被设置。客户端不可能看到部分键已被更新而其他键保持不变的情况。
1 | MSET key1 "Hello" 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
批量获取键的时候需要注意,mset
、mget
目前只支持具有相同 slot 值的 key 执行批量操作。对于映射为不同 slot 值的 key 由于执行 mset
、mget
等操作可能存在于多个节点上因此不被支持。
示例如下:
1 | mset string "value" string2 "value2" |
上面两个命令在使用时都会有如下错误,当尝试在一个命令中使用多个键,但这些键分布在集群的不同槽中时,就会出现这个错误。因为 Redis 集群要求在单个命令中使用的所有键必须位于同一个槽中,以确保操作的原子性和一致性。
1 | CROSSSLOT Keys in request don't hash to the same slot |
解决这个问题,可以使用哈希标签(hash_tag)。键命令执行分为两步:计算槽,查找槽锁对应的节点。在计算槽时,如果键的内容包含大括号字符 {}
,则计算槽的有效部分是大括号中的内容,否则采用键的全部内容来计算槽。键内部大括号包含的内容叫做 hash_tag,它提供不同的键可以具备相同 slot 的功能,常用于 Redis IO 优化。
解决后如下:
1 | mset {string} "value" {string}2 "value2" |
但是,使用哈希标签,又会引入集群倾斜问题。当不同的槽对应键数量差异过大时,会引发集群数据倾斜问题。当大量使用 hash_tag 时,会产生不同的键映射到同一个槽的情况。特别是选择作为 hash_tag 的数据离散度较差时,将加速槽内键数量倾斜情况。
2、时间复杂度
3、字符串的内部编码
字符串类型的内部编码有如下 3 种:
int
,8 字节长整型embstr
,小于等于 39 个字节的字符串raw
,大于 39 个字节的字符串
Redis 会根据当前值的类型和长度决定使用哪种内部编码实现。
整数类型示例如下:
1 | set intkey 2222 |
短字符串示例如下:
1 | set stringkey stringvalue |
长字符串示例如下:
1 | set rawstringkey abcdefghijklmnopqrstuvwxyz-zyxwvutsrqponmlkjihgfedcba |
相关链接
OB links
OB tags
#Redis #未完待续