西梧 runtime

Redis之字符串命令

  • 2022-03-17 19:09:52
  • qtsunami

截止目前,Redis 官方提供的命令有292个。

Redis 为我们提供了一系列丰富的数据结构,从最初的Strings、Hashes、Lists、Sets、Sorted Sets,逐步增加HyperLogLog、BitMaps、Streams、Geo等数据结构。每种数据结构都适用于解决特定的问题。

很多同学在面试的时候,经常会被问到,Redis支持哪些数据结构?然而很多同学的回答依旧停留在五个基础的数据结构:Strings、Hashes、Lists、Sets、Sorted Sets。

然而,Redis从版本2.2版本增加了BitMaps;在2.8.9版本增加了HyperLogLog;3.2及以后版本新增了Geo;在5.0版本新增了Streams。

当被问到最常用的数据结果有哪些?回答Strings、Lists会很多,其他几种类型相对来说就比较少见了。

所以,我整理了下这几种常用数据结构的命令介绍以及应用场景。

本篇文章主要介绍字符串的应用,字符串在实际工作场景中应用最广泛的,我想大家对字符串的应用已经很熟悉了。

大家会的最多的命令可能是GET/SET、MGET/MSET、INCR/DECR等命令。其实截止redis 6.2.5版本,关于字符串应用的命令已经有21个了。

Redis 字符串的命令

下图是我整理的一个关于字符中的命令表及解释:

字符串是以键值对形式进行存储的,在这里我们需求注意的是,我们即可以存储文本数据,也可以是图片、视频、音频甚至压缩文件等比较复杂的二进制数据。

SET中的NX与XX

Redis在默认情况下,对一个已经设置值的key执行SET命令,新值将会覆盖旧值,如下示例:

127.0.0.1:6379> SET hello Wrold
OK
127.0.0.1:6379> SET hello Nice
OK
127.0.0.1:6379> GET hello
"Nice"

如果我们不想被覆盖应该怎么办呢?也许你猜到了,使用NX。

127.0.0.1:6379> SET hello Wrold
OK
127.0.0.1:6379> SET hello NICE NX
(nil)

我们可以看到,如果对已经存在值的key再次执行SET命令,加上NX参数,则会设置失败。

所以,如果用户在执行SET命令时给定了NX选项,SET命令只会在键没有值的情况下设置成功,并返回OK表示成功;相反,如果键存在,SET命令将会放弃执行,并返回空值nil表示失败。

那XX表示什么意思呢?它表示只有在key存在的时候,才会执行,新值覆盖旧值。

127.0.0.1:6379> SET nk meeting XX
(nil)
127.0.0.1:6379> SET nk meeting
OK
127.0.0.1:6379> SET nk meetings XX
OK
127.0.0.1:6379> GET nk
"meetings"

在Redis里还有一个命令 —— SETNX,它的含义和SET使用NX选项意义一样,它会返回1和0代表成功和失败。而我们看到SET在使用NX选项时,是以OK、nil来表示成功失败的。

不过你要注意,Redis里并没有 SETXX 这个命令与SET使用XX选项一致。

GETSET 命令

这是一个很有意思的命令,它就像是GET命令和SET命令的组合,GETSET首先会获取当前key目前已有的值,然后再设置成新值,最后将之前获取到的旧值返回。如果key不存在的话,就返回空值(nil)。

MSET/MGET与SET/GET的区别

MSET 命令也是用于对字符串key-value进行设置,但是它可以一次为多个key设置value值。MGET同理,它可以同时获取多个key的value值。

MSET/MGET与SET/GET命令大部分操作规则是一样的,所以有很多人会有疑问,那我直接多调用几次SET/GET命令不就可以了吗?为什么还要MSET/MGET呢?

MSET/MGET命令除了能够批量设置操作外,另一个关键优势是提高程序的效率:执行多条SET/GET命令需要客户端与服务器之间进行多次网络通信,而MSET/MGET只需要一次网络通信。

同样,对应SETNX命令,也有一个MSETNX命令,只有键不存在的情况下,一次为多个字符串设置值。即使有一个键已经有值了,那么MSETNX命令也会放弃执行。

SETRANGE和GETRANGE:字符串的区间操作

既然是字符串的区间操作,必然会涉及到字符串的索引,对于索引相信大家都明白,只提两点:

  • Redis支持正数索引和负数索引
  • 正数索引以0开始,从字符串开头向结尾依次递增;负数索引以-1开始,从字符串结尾向开头依次递减。

GETRANGE 命令:

GETRANGE key start end

需要注意的是,GETRANGE命令是闭区间索引范围,start和end索引上的值也是返回的结果位。

SETRANGE 命令:

SETRANGE key offset value

这里面可能会有个容易被错误理解的地方,就是从索引开始处到结尾处,如果新值的长度小于可替换的字符串长度,则只会替换对应的长度,这种情况下,原字符串的长度是不变的。

什么情况下原字符串的长度会变化呢?相信大家都能猜到,就是当新值的长度大于可替换字符串长度时,看下面示例就清楚了:

127.0.0.1:6379> SET nk 'hello world'
OK   --- 设置新字符串
127.0.0.1:6379> STRLEN nk
(integer) 11   --- 字符串长度
127.0.0.1:6379> SETRANGE nk 6 'new world'
(integer) 15   --- 从索引6开始,替换字符串
127.0.0.1:6379> GET nk
"hello new world"  --- 设置后的key的新值
127.0.0.1:6379> STRLEN nk
(integer) 15    --- 新值的长度

可以看出来,命令会自动扩展被修改的字符串值。

还有一种情况,如果用户给出索引值超出字符串的长度又会是怎样呢?依旧看一个示例:

127.0.0.1:6379> GET nk
"hello new world"
127.0.0.1:6379> STRLEN nk
(integer) 15
127.0.0.1:6379> SETRANGE nk 17 "new world"
(integer) 26
127.0.0.1:6379> STRLEN nk
(integer) 26
127.0.0.1:6379> GET nk
"hello new world\x00\x00new world"

看明白了吧,nk健的值,多出了几个\x00符号,每个\x00都代表一个空字节,也就是说,字符串值末尾到索引index-1之间的部分会被这些空字节填充。

APPEND:如果key不存在

APPEND就是将新内容追加到末尾。

正常情况下,如果key的值存在,会直接追加到末尾。

如果key不存在呢?放心,不会出错的,Redis会先将key的值设置为空,然后再执行追加操作。效果与SET情况类似。

自增和自减

涉及到增减的命令一共有五个:INCR/DECR/INCRBY/DECRY/INCRBYFLOAT。

从字面也都能理解,前四个都是操作整数的加减,最后一个是操作浮点数的加法。

在会运用这些命令之前,你得先知道Redis什么情况下会认为这个值是数字。

  • 能够使用C语言的long long int类型存储的整数。
  • 能够使用C语言的long double类型存储的浮点数。

INCRBY/DECRBY是对整数值 执行指定整数增量或减量的操作。 INCR/DECR是对整数值执行加1和减1的操作。除固定增量或减量被固定为1之外,其他与INCRBY/DECRBY一致。

INCR key与INCRBY key 1效果是一致的;DECR key与DECRBY key 1效果是一致的。

在使用INCR/DECR和INCRBY/DECRBY系列命令时,需要注意以下几点:

  • key的值需要能被Redis解释为整数,否则命令会返回一个错误。
  • 增量或减量必须能够被Redis解释为整数,也就是数,增量或减量不可以是浮点数
  • 当key不存在时,命令会先将key的值初始化为0,也就是说哪怕key不存在,你也可以使用这些命令。

在使用INCRBYFLOAT时,需要注意的地方:

  • INCRBYFLOAT命令即可以用于浮点数,也可以用于整数。
  • 增量可以是浮点数,也可以是整数。
  • 如果命令返回结果是整数,那执行的结果将会以整数形式存储。
  • 小数点位长度限制17位数,超过这个范围会被截断。

INCRBYFLOAT想进行减法操作怎么办?相信我们都能猜到,增量值使用负值就可以。

所以,INCRBY/DECRBY的增量和减量值,也是可以为负值的,效果就是正常加减法的操作。

STRALGO:对字符串进行复杂算法的操作

STRALGO实现了对字符串进行操作的复杂算法,该命令的目标是向Redis用户提供需要快速实现且大多数编程语言的标准库中通常不提供的算法。

STRALGO LCS algo-specific-argument [algo-specific-argument ...]

目前只实现了LCS一种算法,LCS(longest common substring)是最长公共子串,所以STRALGO的第一个参数只能是LCS。

看下示例,我们就明白目前LCS的使用了:

127.0.0.1:6379> STRALGO LCS STRINGS helloYou helloMe
"hello"
127.0.0.1:6379> MSET key1 helloYou key2 helloMe
OK
127.0.0.1:6379> STRALGO LCS KEYS key1 key2
"hello"
127.0.0.1:6379> STRALGO LCS STRINGS helloAndYou helloOrYme
"helloY"

STRALGO第二个参数有两种:KEYS和STRINGS。

  • KEYS:第三第四个参数,表示以key的形式获取value进行对比
  • STRINGS:表示第三每四个是以值的形式进行对比。

我们再来看看其他用法:

127.0.0.1:6379> MSET key1 ohmytext key2 mynewtext
OK
127.0.0.1:6379> STRALGO LCS KEYS key1 key2
"mytext"
127.0.0.1:6379> STRALGO LCS KYES key1 key2 LEN
(integer) 6
127.0.0.1:6379> STRALGO LCS KEYS key1 key2 IDX
1) "matches"
2) 1) 1) 1) (integer) 4
         2) (integer) 7
      2) 1) (integer) 5
         2) (integer) 8
   2) 1) 1) (integer) 2
         2) (integer) 3
      2) 1) (integer) 0
         2) (integer) 1
3) "len"
4) (integer) 6
127.0.0.1:6379> STRALGO LCS KEYS key1 key2 IDX MINMATCHLEN 4
1) "matches"
2) 1) 1) 1) (integer) 0
         2) (integer) 3
      2) 1) (integer) 0
         2) (integer) 3
3) "len"
4) (integer) 5

可以看到,第一个字符串从索引4-7对应第二次字符串的5-8,即text;第一个字符串从索引2-3对应第二个字符索引0-1,即my。组合起来是mytext。

从上面的结果其实也能看到,字符串的匹配是以倒序的形式开始匹配的。

LEN参数表示返回最长公共子字符串的长度 IDX参数返回一个数组,其中包含LCS长度和两个字符串中的所有范围、每个字符串的起始偏移量和结束偏移量,其中存在匹配项。 MINMATCHLEN num表示最小匹配长度,即每段字符串匹配的长度要大于等于num才会显示。

另外还有一个WITHMATCHLEN参数,当使用WITHMATCHLEN时,表示匹配的每个数组也将具有匹配的长度。

你看:

127.0.0.1:6379> MSET key1 ohmytext key2 mynewtext
OK
127.0.0.1:6379> STRALGO LCS KEYS key1 key2 IDX MINMATCHLEN 2 WITHMATCHLEN
1) "matches"
2) 1) 1) 1) (integer) 4
         2) (integer) 7
      2) 1) (integer) 5
         2) (integer) 8
      3) (integer) 4
   2) 1) 1) (integer) 2
         2) (integer) 3
      2) 1) (integer) 0
         2) (integer) 1
      3) (integer) 2
3) "len"
4) (integer) 6

匹配的数据,每个元素的第三个参数,表示当前匹配的长度。

LCS的算法说明:

  • 字符串的匹配是以倒序的形式开始匹配。
  • 如果没有修饰符,则返回表示最长公共子字符串的字符串。
  • 给出LEN时,该命令返回最长公共子字符串的长度。
  • 当给出IDX时,该命令返回一个数组,其中包含LCS长度和两个字符串中的所有范围、每个字符串的起始偏移量和结束偏移量,其中存在匹配项。
  • 当给定WITHMATCHLEN时,表示匹配的每个数组也将具有匹配的长度。

Redis字符串的应用场景

Redis字符串的应用场景很广泛,我们列举几个比较常用的:

  • 数据缓存。Redis配合其他类型数据库作为缓存层,提升数据读写的效率,降低数据库的压力。这个算是我们在日常开发中最普通的应用了。MySQL有读取有压力了,先别着急,加上Redis缓存。
  • Session共享存储介质。在负载均衡场景下,我需要保证用户在一台服务器登录生成session,所有服务器可以共享,所以可以以redis作为存储介质统一管理。
  • 计数。这个也比较常用,比如验证码防止暴力破解,设置错误五次以后失效;比如限时同一IP对某一接口在单位时间内请求的次数。

还有其他一些应用场景,比如分布式锁,在电商场景下防止库存超卖可能会用到等都可以实现。

© 2023 By 西梧Runtime.    本站博客未经授权禁止转载   |   京ICP备15032626号-1