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

 

小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大。万一有写错的地方,岂不是会误导很多筒子们。所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢!

 

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

 

小喵的个人博客地址: http://www.miaoerduo.com/ ,欢迎随时骚扰~

该博客原地址: http://www.miaoerduo.com/redis/二、redis基本操作-string实战篇.html ,排版应该更精美一点。

 

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

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

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

指令清单:

一,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,对应的值就是你刚刚设置的。

例子:

 1 redis> get name
 2 (nil)
 3 redis> set name miaoerduo
 4 OK
 5 redis> get name
 6 "miaoerduo"
 7 redis> set name miao
 8 OK
 9 redis> get name
10 "miao"

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

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

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

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

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

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

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

以ex为例:

1 redis> set name miaoerduo ex 10
2 OK
3 redis> get name
4 "miaoerduo"
5 redis> get name
6 (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的功能,小喵暂时木有头绪。。。想到应用场景的筒子们可以留言告诉小喵,小喵看到了一定会更新的。

举个例子:

 1 redis> del name
 2 (integer) 0
 3 redis> get name
 4 (nil)
 5 redis> set name miaoerduo nx
 6 OK
 7 redis> set name miaoerduo nx
 8 (nil)
 9 redis> get name
10 "miaoerduo"
11 redis> set name miao xx
12 OK
13 redis> get name
14 "miao"
15 redis> del name
16 (integer) 1
17 redis> set name miao xx
18 (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内部会重新申请内存,完成数据的追加(还记得上一章的内容吗?),这时候数据库由于需要分配内存,可能会出现阻塞(需要分配的内存越大,阻塞时间越长)。

例子:

 1 redis> set str "hello world"
 2 OK
 3 redis> setrange str 6 redis
 4 (integer) 11
 5 redis> get str
 6 "hello redis"
 7 redis> setrange str 15 aha
 8 (integer) 18
 9 redis> get str
10 "hello redis\x00\x00\x00\x00aha"
11 redis> del str
12 (integer) 1
13 redis> setrange str 5 "hello miao"
14 (integer) 15
15 redis> get str
16 "\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中的后一次的结果,而不会是混杂的结果。

例子:

1 redis> mset name1 miaoerduo name2 miao name3 love
2 OK
3 redis> get name1
4 "miaoerduo"
5 redis> get name2
6 "miao"
7 redis> get name3
8 "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都被修改,要么全部都不被修改。

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

 1 redis> get name3
 2 "love"
 3 redis> msetnx name3 miaoerduo name4 miao
 4 (integer) 0
 5 redis> get name4
 6 (nil)
 7 redis> msetnx name4 miaoerduo name5 miao
 8 (integer) 1
 9 redis> get name4
10 "miaoerduo"
11 redis> get name5
12 "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。

演示如下:

 1 redis> get name
 2 (nil)
 3 redis> set name "miaoerduo"
 4 OK
 5 redis> get name
 6 "miaoerduo"
 7 redis> lpush arr 1 2 3 4 5
 8 (integer) 5
 9 redis> get arr
10 (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的话,在传输指令和输出结果的这两个部分就要重复很多次,效率大打折扣。

反派汪:分多次查询,需要传输的指令数目,也不会比单次多很多,为什么一定会影响效率呢?
喵太:其实不然,传输数据需要很多的准备工作,不仅仅是数据的具体传输,有时候需要考虑连接的创建和关闭、设置锁等的开销。
反派汪:说的好像有道理,我再回去研究研究。

1 redis> set key1 a
2 OK
3 redis> set key2 b
4 OK
5 redis> mget key1 key2 key3
6 1) "a"
7 2) "b"
8 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内的结果。

 1 redis> set str "hello world"
 2 OK
 3 redis> getrange str 6 -1
 4 "world"
 5 redis> getrange str -5 -1
 6 "world"
 7 redis> getrange str 0 4
 8 "hello"
 9 redis> getrange str 0 100
10 "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,就能获取计数的值,并且把计数器重置了。

1 redis> set str "hello"
2 OK
3 redis> getset str "world"
4 "hello"
5 redis> get str
6 "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,然后执行追加操作。

 1 redis> set str "hello"
 2 OK
 3 redis> append str " world"
 4 (integer) 11
 5 redis> get str
 6 "hello world"
 7 redis> del str
 8 (integer) 1
 9 redis> append str "hello"
10 (integer) 5
11 redis> get str
12 "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中,在存储这类字符串的时候,底层上其实存储的就是一个整数,因此不存在存储上的浪费。

 1 redis> set count 0
 2 OK
 3 redis> incr count
 4 (integer) 1
 5 redis> incr count
 6 (integer) 2
 7 redis> del count
 8 (integer) 1
 9 redis> get count
10 (nil)
11 redis> incr count
12 (integer) 1
13 redis> incr count
14 (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设成负的就可以。

 1 redis> set a 1.5
 2 OK
 3 redis> incrbyfloat a 10.1
 4 "11.6"
 5 redis> incrbyfloat a 10.1
 6 "21.7"
 7 redis> incrbyfloat a -10.1
 8 "11.6"
 9 redis> incrbyfloat a -1.5e2
10 "-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值。

 1 redis> setbit a 0 1
 2 (integer) 0
 3 redis> setbit a 1 1
 4 (integer) 0
 5 redis> setbit a 2 1
 6 (integer) 0
 7 redis> setbit a 3 1
 8 (integer) 0
 9 redis> get a
10 "\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比特填充的连续空间。

1 redis> setbit a 7 1
2 (integer) 0
3 redis> getbit a 0
4 (integer) 0
5 redis> getbit a 7
6 (integer) 1
7 redis> getbit a 100
8 (integer) 0

3,BITCOUNT key [start end]

 Count set bits in a string

统计key的string的二进制中1的个数。

start和end分别表示string的起始和结束位置,含义和GETRANGE中一样。

1 redis> setbit mykey 0 1
2 (integer) 0
3 redis> setbit mykey 10 1
4 (integer) 0
5 redis> setbit mykey 5 1
6 (integer) 0
7 redis> bitcount mykey
8 (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。

 1 redis> set key1 "\xf0\xf0"
 2 OK
 3 redis> set key2 "\x0f\x0f"
 4 OK
 5 redis> set key3 "\xff\xff"
 6 OK
 7 redis> bitop and destkey key1 key2 key3
 8 (integer) 2
 9 redis> get destkey
10 "\x00\x00"
11 redis> bitop or destkey key1 key2 key3
12 (integer) 2
13 redis> get destkey
14 "\xff\xff"
15 redis> bitop xor destkey key1 key2 key3
16 (integer) 2
17 redis> get destkey
18 "\x00\x00"
19 redis> bitop not destkey key1
20 (integer) 2
21 redis> get destkey
22 "\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。

 

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

 

转载请注明出处~~喵呜~~

 

posted @ 2016-05-03 16:24  喵耳朵  阅读(26967)  评论(9编辑  收藏  举报