SOCSEC

Redis学习笔记

Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

安装

linux 下 使用apt-get install redis 安装

基本使用

启动

1
redis-server --port 6380

Redis服务器默认会使用6379 端口,通过--port参数可以自定义端口号
考虑到Redis 有可能正在将内存中的数据同步到硬盘中,强行终止Redis 进程可能会导致数据丢失。正确停止Redis 的方式应该是向Redis 发送SHUTDOWN 命令,方法为:

1
redis-cli SHUTDOWN

当Redis 收到SHUTDOWN 命令后,会先断开所有客户端连接,然后根据配置执行持久化,最后完成退出。
Redis 可以妥善处理SIGTERM 信号,所以使用“kill Redis 进程的PID”也可以正常结束Redis,效果与发送SHUTDOWN 命令一样。
连接数据库:

1
redis-cli -h 127.0.0.1 -p 6379


1
2
3
4
5
6
redis-cli
redis 127.0.0.1:6379> PING
PONG
redis 127.0.0.1:6379> ECHO hi
"hi"

//Redis 提供了PING 命令来测试客户端与Redis 的连接是否正常,如果连接正常会收到回复PONG。如:

1
2
redis-cli PING
PONG

配置数据库:
配置方法
1.使用redis.conf配置

1
redis-server /path/to/redis.conf

2.通过启动参数配置

1
redis-server /path/to/redis.conf --loglevel warning

3.运行时配置

1
2
127.0.0.1:6379> CONFIG SET loglevel warning
OK

并不是所有参数都可以通过config set 来设置,常见参数配置方式如下:
图0
使用数据库
Redis 是一个字典结构的存储服务器,而实际上一个Redis 实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。
每个数据库对外都是以一个从0 开始的递增数字命名,Redis 默认支持16 个数据库,可以通过配置参数databases 来修改这一数字。客户端与Redis 建立连接后会自动选择0 号数据库,不过可以随时使用SELECT 命令更换数据库,如要选择1 号数据库:

1
2
3
4
redis> SELECT 1
OK
redis [1]> GET foo
(nil)

Redis 不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外Redis 也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL 命令可以清空一个Redis 实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。
redis支持通配符规则:
图01

插入:

1
2
127.0.0.1:6379> SET bar 1
OK

然后使用KEYS *就能获得Redis 中所有的键,KEYS 命令需要遍历Redis 中的所有键,当键的数量较多时会影响性能,不建议在生产环境中使用。
判断键是否存在:

1
EXISTS key

如果键存在则返回整数类型1,否则返回0

1
2
3
4
redis> EXISTS bar
(integer) 1
redis> EXISTS noexists
(integer) 0

删除:

1
DEL key [key …]

获得键值的数据类型:

1
2
3
4
5
6
7
8
redis> SET foo 1
OK
redis> TYPE foo
string
redis> LPUSH bar 1
(integer) 1
redis> TYPE bar
list

LPUSH 命令的作用是向指定的列表类型键中增加一个元素,如果键不存在则创建它

字符串类型

命令:

1.赋值与取值

1
2
SET key value
GET key

图3

2.递增数字,同时创建该key

1
2
3
INCR key
INCRBY key increment

图4

3.减少指定的整数

1
2
3
DECR key
DECRBY key decrement

图5

4.增加指定浮点数

1
INCRBYFLOAT key increment

图6

5.向尾部追加值

1
APPEND key value

APPEND 作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于SET key value。返回值是追加后字符串的总长度。在redis-cli 中输入需要双引号以示区分。
图7

6.获取字符串长度

1
STRLEN key

图8

7.同时获得/设置多个键值

1
2
MGET key [key …]
MSET key value [key value …]

图9

8.位操作

1
2
3
4
GETBIT key offset
SETBIT key offset value
BITCOUNT key [start] [end]
BITOP operation destkey key [key …]

BITOP命令可以对多个字符串类型键进行位运算,并将结果存储在destkey 参数指定的
键中。BITOP 命令支持的运算操作有ANDORXORNOT
图10

散列类型

命令:

1.赋值与取值

1
2
3
4
5
HSET key field value
HGET key field
HMSET key field value [field value …]
HMGET key field [field …]
HGETALL key

图11

2.判断字段是否存在\

1
HEXISTS key field

图12

3.当字段不存在时赋值

1
HSETNX key field value

图13
HSETNX命令与HSET命令类似,区别在于如果字段已经存在,HSETNX命令将不执行任何操作。

4.增加数字

1
HINCRBY key field increment

图14

5.删除字段

1
HDEL key field [field …]

图15

6.只获取字段名或字段值

1
2
HKEYS key
HVALS key

图16

7.获得字段数量

1
HLEN key

图17

字符串类型

命令:

1.向列表两端增加元素

1
2
LPUSH key value [value …]
RPUSH key value [value …]

图18

2.从列表两端弹出元素

1
2
LPOP key
RPOP key

图19

3.获取列表中元素的个数

1
LLEN key

图20

4.获得列表片段

1
LRANGE key start stop

图21
LRANGE命令将返回索引从start 到stop 之间的所有元素(包含两端的元素)。与大多数人的直
觉相同,Redis 的列表起始索引为0. RANGE 返回的值包含最右边的元素.

5.删除列表中指定的值

1
LREM key count value

图22
LREM 命令会删除列表中前count 个值为value 的元素,返回值是实际删除的元素个数。根据count 值的不同,LREM 命令的执行方式会略有差异:
● 当count > 0 时LREM 命令会从列表左边开始删除前count 个值为value的元素;
● 当count < 0 时LREM 命令会从列表右边开始删除前|count|个值为value的元素;
● 当count = 0 是LREM 命令会删除所有值为value 的元素。

6.获得/设置指定索引的元素值

1
LINDEX key index

图23
LINDEX 命令用来返回指定索引的元素,索引从0 开始。如果index 是负数则表示从右边开始计算的索引,最右边元素的索引是-1

1
LSET key index value

LSET 是另一个通过索引操作列表的命令,它会将索引为index 的元素赋值为value

7. 只保留列表指定片段

1
LTRIM key start end

LTRIM 命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和LRANGE 命令相同.
图24

8.向列表中插入元素

1
LINSERT key BEFORE|AFTER pivot value

LINSERT 命令首先会在列表中从左到右查找值为pivot 的元素,然后根据第二个参数是BEFORE 还是AFTER 来决定将value 插入到该元素的前面还是后面。LINSERT 命令的返回值是插入后列表的元素个数。
图25

9.将一个元素从一个列表转到另一个列表

1
RPOPLPUSH source destination

RPOPLPUSH 命令会先从source 列表类型键的右边弹出一个元素,然后将其加入到destination 列表类型键的左边,并返回这个元素的值,整个过程是原子的。
图26

集合类型

【无序集合】
集合中的每个元素都是不同的,且没有顺序。
命令:

1.增加/删除元素

1
2
SADD key member [member …]
SREM key member [member …]

//SADD 命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素已经存在于集合中就会忽略这个元素。本命令的返回值是成功加入的元素数量。SREM 命令用来从集合中删除一个或多个元素,并返回删除成功的个数。

2.获得集合中的所有元素

1
SMEMBERS key

SMEMBERS 命令会返回集合中的所有元素。
图27

3.判断元素是否在集合中

1
SISMEMBER key member

图28

4.集合间运算

1
SDIFF key [key …]

SDIFF 命令用来对多个集合执行差集运算。多个set 时,后一个与前两个差集 进行运算。

1
SINTER key [key …]

SINTER 命令用来对多个集合执行交集运算。多个set时,同上。

1
SUNION key [key …]

SUNION 命令用来对多个集合执行并集运算。多个set时,同上。
图29

5. 获得集合中元素个数

1
SCARD key

图30
SCARD 命令用来获得集合中的元素个数

6.进行集合运算并将结果存储

1
SDIFFSTORE destination key [key …]

图31
SDIFFSTORE 命令和SDIFF 命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在destination 键中。

1
SINTERSTORE destination key [key …]

SDIFFSTORE 功能同上类似,命令常用于需要进行多步集合运算的场景中,如需要先计算差集再将结果和其他键计算交集。

1
SUNIONSTORE destination key [key …]

SINTERSTORE 和SUNIONSTORE 命令与之类似,不再赘述。

7.随机获得集合中的元素

1
SRANDMEMBER key [count]

SRANDMEMBER 命令用来随机从集合中获取count个元素
图32

8.从集合中弹出一个元素

1
SPOP key

//SPOP 命令会从集合中随机选择一个元素弹出。
图33
【有序集合】
有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N 个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。

命令:

1.增加元素

1
ZADD key score member [score member …]

ZADD 命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。ZADD 命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素,即使分数改变了)。

2.获得元素的分数

1
ZSCORE key member

3.获得排名在某个范围的元素列表

1
ZRANGE key start stop [WITHSCORES]

ZRANGE 命令会按照元素分数从小到大的顺序返回索引从start 到stop 之间的所有元素(包含两端的元素)。ZRANGE 命令与LRANGE 命令十分相似,如索引都是从0 开始,负数代表从后向前查找(−1 表示最后一个元素)。如果需要同时获得元素的分数的话可以在ZRANGE 命令的尾部加WITHSCORES 参数。如果两个元素的分数相同,Redis 会按照字典顺序来进行排列。再进一步,如果元素的值是中文怎么处理呢?答案是取决于中文的编码方式,如使用UTF-8 编码。

1
ZREVRANGE key start stop [WITHSCORES]

ZREVRANGE 命令和ZRANGE 的唯一不同在于ZREVRANGE 命令是按照元素分数从大
到小的顺序给出结果的。

4.获得指定分数范围的元素

1
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

按照元素分数从小到大的顺序返回分数在minmax之间(包含minmax)的元素。如果希望分数范围不包含端点值,可以在分数前加上“(”符号。
如:ZRANGEBYSCORE scoreboard 80 (100
minmax 还支持无穷大,同ZADD 命令一样,-inf+inf 分别表示负无穷和正无穷。WITHSCORES 参数的用法与ZRANGE 命令一样,不再赘述。
LIMIT offset count 与 SQL 中的用法基本相同,即在获得的元素列表的基础上向后偏移offset 个元素,并且只获取前count个元素。
图34

5.增加某个元素的分数

1
ZINCRBY key increment member

ZINCRBY 命令可以增加一个元素的分数,返回值是更改后的分数。increment 为增量,负数为减分。

6. 获得集合中元素的数量

1
ZCARD key

7.获得指定分数范围内的元素个数

1
ZCOUNT key min max

8.删除一个或多个元素

1
ZREM key member [member …]

ZREM 命令的返回值是成功删除的元素数量(不包含本来就不存在的元素)。

9.按照排名范围删除元素

1
ZREMRANGEBYRANK key start stop

ZREMRANGEBYRANK 命令按照元素分数从小到大的顺序(即索引0 表示最小的值)删除处在 指定排名范围内的所有元素,并返回删除的元素数量。

10.按照分数范围删除元素

1
ZREMRANGEBYSCORE key min max

ZREMRANGEBYSCORE 命令会删除指定分数范围内的所有元素,参数minmax 的特性和ZRANGEBYSCORE 命令中的一样。返回值是删除的元素数量。

11.获得元素的排名

1
2
ZRANK key member
ZREVRANK key member

ZRANK 命令会按照元素分数从小到大的顺序获得指定的元素的排名(从0 开始,即分数最小的元素排名为0)。

12.计算有序集合的交集

1
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]

ZINTERSTORE命令用来计算多个有序集合的交集并将结果存储在destination 键中(同样以有序集合类型存储),返回值为destination 键中的元素个数。
destination 键中元素的分数是由AGGREGATE 参数决定的
● 当AGGREGATESUM 时(也就是默认值),destination 键中元素的分数是每个参与计算的集合中该元素分数的和。
● 当AGGREGATEMIN 时,destination 键中元素的分数是每个参与计算的集合中该元素分数的最小值。
● 当AGGREGATEMAX 时,destination 键中元素的分数是每个参与计算的集合中该元素分数的最大值。
WEIGHTS 参数设置每个集合的权重,每个集合在参与计算时元素的分数会被乘上该集合的权重。

另外还有一个命令与ZINTERSTORE 命令的用法一样,名为ZUNIONSTORE,它的作
用是计算集合间的并集,这里不再赘述。

进阶

1.事务(transaction)

Redis 中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis 的最小执行单位,一个事务中的命令要么都执行,要么都不执行。事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis 依次执行这些命令。

格式:

1
2
3
4
5
6
7
8
redis> MULTI
OK
redis> other command1
QUEUED
...................
redis> other command2
QUEUED
redis> EXEC

Redis 保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC 命令前客户端断线了,则Redis 会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC 命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。除此之外,Redis 的事务还能保证一个事务内的命令依次执行而不被其他命令插入。

错误处理:
● 语法错误
只要有一个命令有语法错误,执行EXEC命令后Redis就会直接返回错误,连语法正确的命令也不会执行
● 运行错误
事务里其他的命令依然会继续执行(包括出错命令之后的命令)。
Redis的事务没有关系数据库事务提供的回滚(rollback)功能。为此开发者必须在事务执行出错后自己收拾剩下的摊子(将数据库复原回事务执行前的状态等)。

WATCH命令
WATCH 命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC 之后才执行的,所以在MULTI 命令后可以修改WATCH 监控的键值)。执行EXEC 命令后会取消对所有键的监控,如果不想执行事务中的命令也可以使用UNWATCH 命令来取消监控

2. 生存时间

在Redis 中可以使用EXPIRE 命令设置一个键的生存时间,到时间后Redis 会自动删除它。
使用方法:

1
EXPIRE key seconds

其中seconds 参数表示键的生存时间,单位是秒。
如果想知道一个键还有多久的时间会被删除,可以使用TTL 命令。返回值是键的剩余时间(单位是秒):

1
TTL foo

当键不存在时TTL 命令会返回−1。另外同样会返回−1 的情况是没有为键设置生存时间(即永久存在,这是建立一个键后的默认情况)。
如果想取消键的生存时间设置(即将键恢复成永久的),可以使用PERSIST 命令。如果生存时间被成功清除则返回1;否则返回0(因为键不存在或键本来就是永久的):

1
PERSIST foo

除了PERSIST 命令之外,使用SETGETSET 命令为键赋值也会同时清除键的生存时间,再次使用EXPIRE 命令会重新设置键的生存时间。其他只对键值进行操作的命令(如INCRLPUSHHSETZREM)均不会影响键的生存时间。
EXPIRE 命令的seconds 参数必须是整数,所以最小单位是1 秒。如果想要更精确的控制键的生存时间应该使用PEXPIRE 命令,PEXPIRE 命令与EXPIRE 的唯一区别是前者的时间单位是毫秒,即PEXPIRE key 1000EXPIRE key 1 等价。对应地可以用PTTL命令以毫秒为单位返回键的剩余时间。如果使用WATCH 命令监测了一个拥有生存时间的键,该键时间到期自动删除并不会被WATCH 命令认为该键被改变。
另外还有两个相对不太常用的命令:EXPIREATPEXPIREATEXPIREAT 命令与EXPIRE 命令的差别在于前者使用Unix 时间作为第二个参数表示键的生存时间的截止时间。PEXPIREAT 命令与EXPIREAT 命令的区别是前者的时间单位是毫秒。

3.排序

命令:

1
SORT KEYS [ALPHA]

SORT 命令可以对列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的连接查询相类似的任务。在对有序集合类型排序时会忽略元素的分数,只针对元素自身的值进行排序。
除了可以排列数字外,SORT 命令还可以通过ALPHA 参数实现按照字典顺序排列非数字元素.如果没有加ALPHA 参数的话,SORT 命令会尝试将所有元素转换成双精度浮点数来比较,如果无法转换则会提示错误。SORT 命令还支持LIMIT 参数来返回指定范围的结果。用法和SQL 语句一样,LIMIT offset count,表示跳过前offset 个元素并获取之后的count 个元素。

BY 参数的语法为“BY 参考键”。
其中参考键可以是字符串类型键或者是散列类型键的某个字段(表示为键名->字段名)。如果提供了BY 参数,SORT 命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序.当参考键名不包含“*”时(即常量键名,与元素值无关),SORT 命令将不会执行排序操作,因为Redis 认为这种情况是没有意义的(因为所有要比较的值都一样).
GET 参数不影响排序,它的作用是使SORT 命令的返回结果不再是元素自身的值,而是GET 参数中指定的键值。GET 参数的规则和BY 参数一样,GET 参数也支持字符串类型和散列类型的键,并使用“*”作为占位符。
STORE 参数默认情况下SORT 会直接返回排序结果,如果希望保存排序结果,可以使用STORE 参
数。保存后的键的类型为列表类型,如果键已经存在则会覆盖它。加上STORE 参数后SORT命令的返回值为结果的个数。STORE 参数常用来结合EXPIRE 命令缓存排序结果.

4.消息通知

任务队列

BRPOP 命令。BRPOP 命令和RPOP 命令相似,唯一的区别是当列表中没有元素时BRPOP 命令会一
直阻塞住连接,直到有新元素加入。

1
BRPOP queue timeout

BRPOP 命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了
此时间仍然没有获得新元素的话就会返回nil。除了BRPOP 命令外,Redis 还提供了BLPOP,和BRPOP 的区别在与从队列取元素时BLPOP 会从队列左边取。具体可以参照LPOP 理解,这里不再赘述。

优先级队列

BRPOP 命令可以同时接收多个键,其完整的命令格式为

1
BLPOP key [key …] timeout,

意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中有一个键有元素则会从该键中弹出元素。

“发布/订阅”模式
发布/订阅”模式同样可以实现进程间的消息传递
PUBLISH 命令用法:

1
PUBLISH channel message

订阅频道的命令是 SUBSCRIBE,可以同时订阅多个频道,用法是

1
SUBSCRIBE channel [channel …]

执行 SUBSCRIBE 命令后客户端会进入订阅状态,处于此状态下客户端不能使用除SUBSCRIBE/UNSUBSCRIBE/PSUBSCRIBE/PUNSUBSCRIBE 这4 个属于“发布/订阅”模式的命令之外的命令(后面3 个命令会在下面介绍),否则会报错。
进入订阅状态后客户端可能收到三种类型的回复。每种类型的回复都包含 3 个值,第一个值是消息的类型,根据消息类型的不同,第二、三个值的含义也不同。消息类型可能的取值有:
(1)Subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个值是当前客户端订阅的频道数量。
(2)message。这个类型的回复是我们最关心的,它表示接收到的消息。第二个值表示产生消息的频道名称,第三个值是消息的内容
(3)unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0 时客户端会退出订阅状态,之后就可以执行其他非“发布/订阅”模式的命令了。
使用 UNSUBSCRIBE 命令可以取消订阅指定的频道,用法为

1
UNSUBSCRIBE [channel[channel …]]

如果不指定频道则会取消订阅所有频道

除了可以使用SUBSCRIBE 命令订阅指定名称的频道外,还可以使用PSUBSCRIBE 命令订阅指定的规则。规则支持glob 风格通配符格式
使用PSUBSCRIBE 命令可以重复订阅一个频道,如某客户端执行了

1
PSUBSCRIBE channel.? channel.?*

这时向channel.2 发布消息后该客户端会收到两条消息,而同时PUBLISH 命令返回的值也是2 而不是1。同样的,如果有另一个客户端执行了SUBSCRIBE channel.10,和PSUBSCRIBE channel.?*的话,向channel.10发送命令该客户端也会收到两条消息(但是是两种类型,message pmessage),同时PUBLISH 命令会返回2。

PUNSUBSCRIBE 命令可以退订指定的规则,用法是

1
PUNSUBSCRIBE [pattern[pattern …]]

如果没有参数则会退订所有规则。

使用PUNSUBSCRIBE 命令只能退订通过PSUBSCRIBE 命令订阅的规则,不会影响直接通过SUBSCRIBE 命令订阅的频道;同样UNSUBSCRIBE 命令也不会影响通过PSUBSCRIBE 命令订阅的规则。另外容易出错的一点是使用PUNSUBSCRIBE 命令退订某个规则时不会将其中的通配符展开,而是进行严格的字符串匹配,所以PUNSUBSCRIBE *无法退订channel.*规则,而是必须使用PUNSUBSCRIBE channel.* 才能退订。

5 python与Redis

Redis官方推荐的Python客户端是redis-py
安装:

1
pip install redis

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//引入包
import redis
//连接数据库
r= redis.StrictRedis()
//显式连接
r= redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
//SET/GET用法
r.set('foo', 'bar') # True
r.get('foo') # 'bar'
//HMSET 支持将字典作为参数存储,同时HGETALL 的返回值也是一个字典
r.hmset('dict', {'name': 'Bob'})
people = r.hgetall('dict')
print people # {'name': 'Bob'}
//事务
pipe = r.pipeline()
pipe.set('foo', 'bar')
pipe.get('foo')
result = pipe.execute()
print result # [True, 'bar']
//管道的使用方式和事务相同,只不过需要在创建时加上参数transaction=False
pipe = r.pipeline(transaction=False)
//事务和管道还支持链式调用
result = r.pipeline().set('foo', 'bar').get('foo').execute()
# [True, 'bar']