二、Redis基本操作——String(实战篇)

小喵的唠叨话:上一周因为实验室要用GIT作为版本控制器,导致小喵花了一周的时间研究GIT,Redis这边,就没来得及继续研究。前几天看到有人搜索Redis怎么向数组里面添加内容,搜到了我的上一篇文章,可惜上一篇只讲到了String的原理,所以小喵痛定思痛,决定还是爬起来写这篇博客,免得搜索String操作的小伙伴又空手而归。

上一篇博客,我们详细的介绍了Redis中String数据类型的底层实现,相信大家已经在原理上掌握的相当不错了。这次,我们就介绍Redis的命令行操作。当然,我们实际开发的时候可能不会太经常直接用到Redis的命令,而是通过一些数据库的封装来操控(就像我们虽然会使用SQL,但是实际开发的时候,总是有一些特别好用的库,既安全又方便)。但是,学习最基础的Redis命令,是针对各种语言的以不变应万变的法宝,毕竟库是针对于语言的。同时,学会了Redis的基本命令,将来对各种库的掌握也能更迅速。

关于Redis的命令,主要参考Redis的官方文档(http://redis.io/commands#string)。小喵这里主要像是翻译一下,并且给出一些实用的例子。排版上,小喵按功能重新组织了一下指令,并将比较常用的指令放在了最前面。同时也增加了锚点,方便大家查阅。小喵这里的Redis版本是3.0.7,应该是目前的最新版。

注:redis的命令(SET,GET等)是不区分大小写的(KEY和VALUE区分的),为了方便所以小喵的操作可能都是小写的。

指令清单:

  • APPEND
  • BITCOUNT
  • BITOP
  • BITPOS
  • DECR
  • DECRBY
  • GET
  • GETBIT
  • GETRANGE
  • GETSET
  • INCR
  • INCRBY
  • INCRBYFLOAT
  • MGET
  • MSET
  • MSETNX
  • SET
  • SETBIT
  • SETEX
  • SETNX
  • SETRANGE
  • STRLEN

一,SET的相关操作

1,SET key value [EX seconds] [PX milliseconds] [NX|XX]

Set the string value of a key

将键key设置成指定的字符串value。

我们知道Redis是一种key-value数据库(当然这么说可能不够严谨,因为Redis支持很多高级的数据结构)。因此所有的数据都是通过key来访问,key就是键。

SET指令可以将字符串的value和key绑定在一起。

如果不加额外的参数。当key中已经保存了一个值的话,这个值会被覆盖成新的值,并且忽略掉原始类型(一个key对应的不一定是一个字符串,也可以是List等其他的数据结构,这些结构在后面的文章里面会陆续介绍);如果key不存在,那么则会在数据库中新增一个key,对应的值就是你刚刚设置的。

例子:

redis> get name
(nil)
redis> set name miaoerduo
OK
redis> get name
"miaoerduo"
redis> set name miao
OK
redis> get name
"miao"

GET命令用于查看key对应的值,我们下面会讲到。

第一个get,由于name这个key不存在,因此我们得到的结果是nil,也就是说空。

之后我们设置一次,然后就get到我们的结果了。之后再设置一次,会发现结果已经被覆盖。

这就是set命令的基本用法。

我们看到文档的后面有一些可选的参数。下面依次来介绍:

EX seconds:设置key的过时时间,单位为秒。

PX milliseconds:设置key的过期时间,单位为毫秒。

以ex为例:

redis> set name miaoerduo ex 10
OK
redis> get name
"miaoerduo"
redis> get name
(nil)

我们设置了name的过期时间为10秒。

在设置完name之后,立刻获取name的值,可以得到正确的输出。但是过了10s之后,发现只能得到一个nil。说明这个key已经被清空了。

那么这个设置有什么作用呢?

我们说几个常见的应用场景:

a,验证码

我们经常在登录一些网站或是进行付款等操作的时候,都会收到一些验证码,并且说10min后失效。

实际上就可以通过下面一条指令来实现:

set phone_num code ex 600

用手机号作为key,验证码作为值,超时6min。这样当你输入好验证码,提交的时候,后台就可以了先 get phone_num,再比较你的输入和数据库里面存的值,从而完成身份的验证。

b,session

早前,网站通过cookie来保存用户的用户名和密码,之后出现了很多的安全隐患,因此就提出了session的机制。

用户登陆成功之后,本地的cookie会保留一个较长的随机码,而网站后的后台则存储了这个随机码和你的用户id的对应关系。在你第二次登录的时候,cookie会传输到后台,而后台则根据你的随机码,获取你的用户信息,如果符合,则自动登录。这样,即使网站上有不法分子获取了你的cookie,也得不到你的任何信息,因为你的真实的有用的信息都存储在网站的后台。

我们在登录邮箱的时候,通常都会有一个选项,7天内自动登录。这其实就是给后台存的session设置了一个超时。聪明的你是不是已经会自己实现了呢?

NX:(if Not eXist)只有键key不存在的时候才会设置key的值

XX:只有键key存在的时候才会设置key的值 NX通常用于实现锁机制,XX的功能,小喵暂时木有头绪。。。想到应用场景的筒子们可以留言告诉小喵,小喵看到了一定会更新的。

举个例子:

redis> del name
(integer) 0
redis> get name
(nil)
redis> set name miaoerduo nx
OK
redis> set name miaoerduo nx
(nil)
redis> get name
"miaoerduo"
redis> set name miao xx
OK
redis> get name
"miao"
redis> del name
(integer) 1
redis> set name miao xx
(nil)

首先我们使用del指令删除了name(这个指令后面会介绍)。之后使用nx模式设置name,第一次成功了,而第二次就失败了,因为name已经存在了。之后使用xx模式修改,发现确实可以。del name之后,再使用xx模式,就失败了,因为此时的name已经不存在了。

以上,就是set的所有的用法。

2,SETRANGE key offset value

Overwrite part of a string at key starting at the specified offset

这个命令可以覆盖掉key对应的string的一部分。offset表示需要覆盖的字符串的起始位置,之后会用value的值,覆盖掉原始string的对应位置的数据。

这里有一些比较有意思的操作:如果原始key不存在,则默认为一个长度为0的字符串。如果offset超过原始string的长度,那么会在之前的string后面补充0以达到offset。如果value的长度超过了原始string后面可以覆盖的部分,则Redis内部会重新申请内存,完成数据的追加(还记得上一章的内容吗?),这时候数据库由于需要分配内存,可能会出现阻塞(需要分配的内存越大,阻塞时间越长)。

例子:

redis> set str "hello world"
OK
redis> setrange str 6 redis
(integer) 11
redis> get str
"hello redis"
redis> setrange str 15 aha
(integer) 18
redis> get str
"hello redis\x00\x00\x00\x00aha"
redis> del str
(integer) 1
redis> setrange str 5 "hello miao"
(integer) 15
redis> get str
"\x00\x00\x00\x00\x00hello miao"

在这个实验中,我们先新建了一个str,内容是"hello world",之后,从位置6开始写入字符串"redis",则得到了"hello redis"。之后我们在15的位置,写入"aha",这是offset已经比字符串的长度要大了,则Redis会默认填充0(\x00是0的16进制表达),之后再追加字符串。最后,我们给一个不存在的key使用setrange设置了一个值,结果表现得和空字符串一样。

3,MSET key value [key value ...]

Set multiple keys to multiple values

一次性设置多个key-value。如果key的值已存在,则会直接覆盖。 相当与同时调用多次SET命令。不过要注意的是,这个操作是原子的。也就是说,所有的keys都一次性设置的。如果你同时运行两个MSET来设置相同的keys,那么操作的结果也只会是两次MSET中的后一次的结果,而不会是混杂的结果。

例子:

redis> mset name1 miaoerduo name2 miao name3 love
OK
redis> get name1
"miaoerduo"
redis> get name2
"miao"
redis> get name3
"love"

4,MSETNX key value [key value ...]

Set multiple keys to multiple values, only if none of the keys exist

一次性设置多个key-value。如果存在任何一个key已经存在,那么这个操作都不会执行。所以,当使用MSETNX的时候,要么全部key都被修改,要么全部都不被修改。

当然这个操作也是原子的。

redis> get name3
"love"
redis> msetnx name3 miaoerduo name4 miao
(integer) 0
redis> get name4
(nil)
redis> msetnx name4 miaoerduo name5 miao
(integer) 1
redis> get name4
"miaoerduo"
redis> get name5
"miao"

当name3已经存在的时候,我们设置name3和name4,发现,连name4都没有创建。当设置name4和name5的时候,由于这两个key之前都不存在,因此设置成功了。

5,SETEX key seconds value

Set the value and expiration of a key

设置key的值和超时。和前面的set key value ex seconds一样。

6,PSETEX key milliseconds value

Set the value and expiration in milliseconds of a key

设置key的值和超时。和前面的set key value px milliseconds一样。

7,SETNX key value

Set the value of a key, only if the key does not exist

当key不存在的时候,设置key的值。和set key value nx一样。

二、GET的相关操作

1,GET key

Get the value of a key

根据给定的key,获取value值。这个操作,我们之前已经使用了很多次。

如果key不存在,会返回nil。如果key对应的值不是string(List,Set等),则会报错,因为GET只能处理string类型的value。

演示如下:

redis> get name
(nil)
redis> set name "miaoerduo"
OK
redis> get name
"miaoerduo"
redis> lpush arr 1 2 3 4 5
(integer) 5
redis> get arr
(error) WRONGTYPE Operation against a key holding the wrong kind of value

注意,lpush是List部分的内容,arr的值是一个List的结构。这里只需要知道key不是string类型的时候,get的时候就会报错。

2,MGET key [key ...]

Get the values of all the given keys

还记得我们之前可以同时设置多个key-value吗?其实我们也可以一次性获取多个key的值。如果key不存在,则对应的地方返回nil。

那么一次性获取多个值和单独一次一次GET有什么区别呢?

小喵认为,主要有两点,原子操作和查询效率。

比如博客上会统计评论数和访问数。如果我们依次读取这两部分的值,那么可能得到的两个值并不是同一时刻的。而如果使用MGET,则得到的一定是同一时刻的。这就是原子操作的威力(注,官方文档只介绍了SET和GET的操作是原子的,并没有说MGET是不是,这里小喵果断的说MGET是原子操作也是不合理的。但考虑到使用MGET的时候,是把查询指令一次性传输到后台来执行,所以应该原子操作。无论如何,在上面的例子中,使用MGET总是比两次GET要合理的)。

另外,如果数据库的查询,都分为三个过程,传输查询指令,执行指令,输出结果。

如果分多次GET的话,在传输指令和输出结果的这两个部分就要重复很多次,效率大打折扣。

redis> set key1 a
OK
redis> set key2 b
OK
redis> mget key1 key2 key3
1) "a"
2) "b"
3) (nil)

我们只创建了key1和key2,使用MGET获取的时候,由于没有key3,对应的位置返回了nil。

3,GETRANGE key start end

Get a substring of the string stored at a key

该指令只要用于获取字符串的字串,在Redis2.0版本之前,叫做SUBSTR。strat和end是字串的起始和结束的位置,可以用负数来表示距离string尾部的未知的下标。-1是最后一个字符,-2是底数第二个字符。这个表达方式和Python的获取list的子list非常相似。

需要注意的有两点:

字串包括了start和end这两个位置的字符。在Python中是不包含end的。

当给出的start和end超出了string的范围时,指令只会返回在string内的结果。

redis> set str "hello world"
OK
redis> getrange str 6 -1
"world"
redis> getrange str -5 -1
"world"
redis> getrange str 0 4
"hello"
redis> getrange str 0 100
"hello world"

上述例子是几种情况下的输出。

4,GETSET key value

Set the string value of a key and return its old value

设置key的值,并返回之前的值。如果之前key的值不是string,则会报错。

这个指令相当于先GET,再SET。

这个指令可以用来配合INCR指令一起使用支持重置的技术功能(INCR我们后面会讲到)。先设置count为0,每次INCR使得count加1。等到需要获取计数的时候,使用GETSET count 0,就能获取计数的值,并且把计数器重置了。

redis> set str "hello"
OK
redis> getset str "world"
"hello"
redis> get str
"world"

第一行设置str为"hello"

第二行获取了str原先的值,并把str设置成"world"。

第三行GET的时候,就是修改之后的值了。

三、string的修改操作

1,STRLEN key

Get the length of the value stored in a key

返回key对应的string的长度,如果key对应的不是string,则报错。如果key不存在,则返回0(还是把key对应的看成空字符串)。

redis> set str "hello world"
OK
redis> strlen str
(integer) 11
redis> get nokey
(nil)
redis> strlen nokey
(integer) 0

2,APPEND key value

Append a value to a key

如果key已经存在,且值为string,则将value追加到值的后面,如果key不存在,则会创建一个空的字符串的key,然后执行追加操作。

redis> set str "hello"
OK
redis> append str " world"
(integer) 11
redis> get str
"hello world"
redis> del str
(integer) 1
redis> append str "hello"
(integer) 5
redis> get str
"hello"

这个例子中,我们先向已有值的str中append了一个字符串。然后向不存在的key,也添加了字符串。

3,INCR key

Increment the integer value of a key by one

对存储在key的整数值进行原子的加1操作。

如果key不存在,则会设置默认值0,再加1。

如果key存在,但是存储的值不是字符串,或者存储的字符串不能表示整数,则执行该操作时,会报错。

这个操作仅限于64位的有符号的整型数据。

比较有意思的是,虽然这个key存储的值是个字符串,但是该操作的效果和对相应的数字进行操作一样。而且,Redis中,在存储这类字符串的时候,底层上其实存储的就是一个整数,因此不存在存储上的浪费。

redis> set count 0
OK
redis> incr count
(integer) 1
redis> incr count
(integer) 2
redis> del count
(integer) 1
redis> get count
(nil)
redis> incr count
(integer) 1
redis> incr count
(integer) 2

值得注意的是,该操作是原子操作,即使有多个请求传输到Redis,count执行的结果都不会错误,所以我们可以大胆放心的用这个功能实现多线程的计数功能。

4,DECR key

Decrement the integer value of a key by one 对存储在key的整数值进行原子的减1操作。

注意事项和INCR一样。

5,INCRBY key increment

Increment the integer value of a key by the given amount

对存储在key的整数值进行原子的加操作,加increment。

如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的整型数字。基本上和INCR一样。

6,DECRBY key decrement

Decrement the integer value of a key by the given number

对存储在key的整数值进行原子的减操作,减increment。

其他和INCR一样。

7,INCRBYFLOAT key increment

Increment the float value of a key by the given amount

对存储造key中的浮点数进行原子的加操作,加increment。

如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成浮点数的字符串,就返回错误。

我们并没有DECRBYFLOAT这个操作,因此想要实现减操作,只需要把increment设成负的就可以。

redis> set a 1.5
OK
redis> incrbyfloat a 10.1
"11.6"
redis> incrbyfloat a 10.1
"21.7"
redis> incrbyfloat a -10.1
"11.6"
redis> incrbyfloat a -1.5e2
"-138.39999999999999999"

浮点数可以用一般的小数和科学计数法表示。

四、二进制操作

1,SETBIT key offset value

Sets or clears the bit at offset in the string value stored at key

设置或者清空key的value(字符串)在offset处的bit值。这里将string看成由bit组成的数组。

指定位置的bit可以被设置,或者被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串足够长到在offset处有bit值。参数offset需要大于等于0,并且小于2^32(限制bitmap大小为512)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。

该操作返回value原来的offset位置的bit值。

redis> setbit a 0 1
(integer) 0
redis> setbit a 1 1
(integer) 0
redis> setbit a 2 1
(integer) 0
redis> setbit a 3 1
(integer) 0
redis> get a
"\xf0"

a最开始不存在,使用setbit操作,将a的前4位都设置成1。最终就得到了\xf0,这是16进制表示的结果,前4位都是1,其他都是0。

2,GETBIT key offset

Returns the bit value at offset in the string value stored at key

获取key对应的string在offset处的bit值。当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。

redis> setbit a 7 1
(integer) 0
redis> getbit a 0
(integer) 0
redis> getbit a 7
(integer) 1
redis> getbit a 100
(integer) 0

3,BITCOUNT key [start end]

Count set bits in a string

统计key的string的二进制中1的个数。 start和end分别表示string的起始和结束位置,含义和GETRANGE中一样。

redis> setbit mykey 0 1
(integer) 0
redis> setbit mykey 10 1
(integer) 0
redis> setbit mykey 5 1
(integer) 0
redis> bitcount mykey
(integer) 3

4,BITOP operation destkey key [key ...]

Perform bitwise operations between strings

对string进行bit级别的操作。具体操作有4种。AND,OR,XOR,NOT。

用法如下:

BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN

BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN

BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN

BITOP NOT destkey srckey NOT

操作后面只有一个目标key和srckey,是因为NOT操作是一元的。

对于AND,OR和XOR操作,Redis会将srckey1,srckey2,...,srckeyN这些字符串对位进行相关操作,之后将结果存入destkey中。

如果srckey的length不相等的话,Redis内部会将短的字符串补齐,并填充上0。

redis> set key1 "\xf0\xf0"
OK
redis> set key2 "\x0f\x0f"
OK
redis> set key3 "\xff\xff"
OK
redis> bitop and destkey key1 key2 key3
(integer) 2
redis> get destkey
"\x00\x00"
redis> bitop or destkey key1 key2 key3
(integer) 2
redis> get destkey
"\xff\xff"
redis> bitop xor destkey key1 key2 key3
(integer) 2
redis> get destkey
"\x00\x00"
redis> bitop not destkey key1
(integer) 2
redis> get destkey
"\x0f\x0f"

5,BITPOS key bit [start] [end]

Find first bit set or clear in a string

返回string的二进制中第一个0或1的位置。

这里将string看成一个有许多bit组成的数组。其中start和end表示string的一个间隔,如果指定了start和end,则BITPOS只会查询这个区间。注意,start和end表示的字符的位置,不是bit的位置。

即使指定了start和end,BITPOS这个操作也只会这个目标bit的绝对地址。

有几点需要注意:

在没有指定查询区间或只指定start的时候,查询bit位为0的位置时,如果string中没有该位,则会返回string的bit位的总数。比如在\xff\xff\xff直接查询bit为0的位置,Redis默认该字符串后面都是0,因此,返回的结果就是12(下标从0开始数的)。

如果指定了查询区间,无论查询0或是1,在没查询到的时候只会返回-1。

在没有指定查询区间时,查询bit位为1的位置时,如果string中没有该位,则会返回-1,表示未查询到。

redis> set bits "\x00\xff\x00\xff"
OK
redis> strlen bits
(integer) 4
redis> bitpos bits 0
(integer) 0
redis> bitpos bits 1
(integer) 8
redis> bitpos bits 0 1
(integer) 16
redis> bitpos bits 0 1 -1
(integer) 16
redis> bitpos bits 1 1 -1
(integer) 8
redis> bitpos bits 0 3
(integer) 32
redis> bitpos bits 0 3 3
(integer) -1

看上面的例子,bits的初始设置的二进制表示为:

00000000 11111111 00000000 11111111

直接获取0(bitpos bits 0)的位置为0,获取1(bitpos bits 1)的位置为8。

指定开始位置为1(start = 1)的时候,0第一次出现(bitpos bits 1)的位置为16。

指定起始位置为3(start = 3)的时候,0第一次出现(bitpos bits 0 3)的位置为32。

这是因为Redis在查询字符串的时候查询到了字符串的末尾,之后默认末尾的后面都是0,因此得到了32这的个位置。

最后,当指定必须在第3个字节(从0开始数)查询0的时候,由于查不到0,因此只返回了-1。

这篇博客的内容,小喵自己也感觉有点多,每个指令小喵都是参考了官方的文档,然后都自己动手做了实验。可能有些地方,小喵没有理解的很好,这时就需要大家多多指教了。

转载请注明出处~