Redis缓存存储

Redis 是一个基于内存的、高效的键值型非关系型数据库,存取效率极高,而且支持多种数据存储结构,使用起来也非常简单。本节我们就来介绍一下 Python 的 Redis 操作,主要介绍 redis-py 这个库的用法。

准备工作

在开始之前,请确保已经安装好了 Redis 并能正常运行,安装方式可以参考: https://setup.scrape.center/redis

除了安装好 Redis 数据库外,我们还需要安装好 redis-py 库,即用来操作 Redis 的 Python 包,可以使用 pip3 来安装:

pip3 install redis

更详细的安装说明可以参考: https://setup.scrape.center/redis-py

安装好 Redis 数据库和 redis-py 库之后,我们便可以开始本节的学习了。

Redis和StrictRedis

redis-py 库提供 Redis 和 StrictRedis 两个类,用来实现 Redis 命令对应的操作。

StrictRedis 类实现了绝大部分官方的 Redis 命令,参数也一一对应,例如 set 方法就对应 Redis 命令的 set 方法。而 Redis 类是 StrictRedis 类的子类,其主要功能是向后兼容旧版本库里的几个方法。为了实现兼容,Redis 类对方法做了改写,例如将 lrem 方法中 valuenum 参数的位置互换,这和 Redis 命令行的命令参数是不一致的。

官方推荐使用 StrictRedis 类,所以本节我们也用 StrictRedis 类的相关方法作为演示。

连接Redis

我们先在本地安装好 Redis,并运行在 6379 端口,将密码设置为 foobared。可以用如下实例连接 Redis 并测试:

from redis import StrictRedis

redis = StrictRedis(host='localhost', port=6379, db=0, password='foobared')
redis.set('name', 'Bob')
print(redis.get('name'))

这里我们传入了 Redis 的地址、运行端口、使用的数据库和密码信息。在默认不传数据的情况下,这 4 个参数分别为 localhost63790None。然后声明了一个 StrictRedis 对象,并调用对象的 set() 方法,设置了一个键值对。最后调用 get 方法获取了设置的键值并打印出来。

运行结果如下: b’Bob

这说明我们成功连接了 Redis,并且可以执行 setget 操作了。

当然,还可以使用 ConnectionPool 来连接 Redis,实例代码如下:

from redis import StrictRedis, ConnectionPool

pool = ConnectionPool(host='localhost', port=6379, db=0, password='foobared')
redis = StrictRedis(connection_pool=pool)

这样的连接效果是一样的。观察源码可以发现,StrictRedis 内其实就是用 hostport 等参数又构造了一个 ConnectionPool,所以直接将 ConnectionPool 当作参数传给 StrictRedis 也一样。

另外,ConnectionPool 还支持通过 URL 来构建连接。URL 支持的格式有如下 3 种:

redis://[:password]@host:port/db
rediss://[:password]@host:port/db
unix://[:password]@/path/to/socket.sock?db=db

这 3 种 URL 分别表示创建 Redis TCP 连接、Redis TCP+SSL 连接、Redis UNIX socket 连接。我们只需要构造其中任意一种即可,其中 password 部分如果有则可以写上,如果没有也可以省略。下面再用 URL 连接演示一下:

url = 'redis://:foobared@localhost:6379/0'
pool = ConnectionPool.from_url(url)
redis = StrictRedis(connection_pool=pool)

这里我们使用的是第一种格式。首先,声明一个 Redis 连接字符串,然后调用 from_url 方法创建 ConnectionPool,接着将其传给 StrictRedis 即可完成连接,所以使用 URL 的连接方式还是比较方便的。

键操作

表4-5 总结了键的一些判断和操作方法。

Table 1. 表 4-5 键的一些判断和操作方法
方法 作用 参数说明 实例 实例说明 实例结果

exists(name)

判断一个键是否存在

name: 键名

redis.exists('name')

是否存在 name 这个键

True

delete(name)

删除一个键

name: 键名

redis.delete('name')

删除 name 这个键

1

type(name)

判断键类型

name: 键名

redis.type('name')

判断 name 这个键的类型

b’string'

keys(pattern)

获取所有符合规则的键

pattern: 匹配规则

redis.keys('n*')

获取所有以 n 为开头的键

[b’name']

randomkey()

获取随机的一个键

randomkey()

获取随机的一个键

b’name'

rename(src, dst)

对键重命名

src: 原键名, dst: 新键名

redis.rename('name', 'nickname')

将 name 重命名为 nickname

True

dbsize()

获取当前数据库中键的数目

dbsize()

获取当前数据库中键的数目

100

expire(name, time)

设定键的过期时间,单位为秒

name: 键名, time: 秒数

redis.expire('name', 2)

将 name 键的过期时间设置为 2 秒

True

ttl(name)

获取键的过期时间,单位为秒

name: 键名

redis.ttl('name')

获取 name 这个键的过期时间

1 (1 表示永久不过期)

move(name, db)

将键移动到其他数据库

name: 键名, db: 目标数据库

move('name', 2)

将 name 键移动到 2 号数据库

True

flushdb()

删除当前所选数据库中的所有键

flushdb()

删除当前所选数据库中的所有键

True

flushall()

删除所有数据库中的所有键

flushall()

删除所有数据库中的所有键

True

字符串操作

Redis 支持最基本的键值对存储,相关方法的总结如表 4-6 所示。

Table 2. 表 4-6 键值对存储的相关方法
方法 作用 参数说明 实例 实例说明 实例结果

set(name, value)

将数据库中指定键名为 name 的键值赋为字符串 value

name: 键名
value: 值

redis.set('name', 'Bob')

将 name 这个键对应的值赋为 Bob

True

get(name)

返回数据库中指定键名 name 对应的键值

name: 键名

redis.get('name')

返回 name 这个键对应的键值

b’Bob'

getset(name, value)

将数据库中指定键名为 name 的键值赋为字符串 value,并返回上次的 value

name: 键名
value: 新值

redis.getset('name', 'Mike')

将 name 这个键对应的键值赋为 Mike,并返回上次的值

b’Bob'

mget(keys, *args)

返回由多个键对应的 value 组成的序列列表

keys: 键名

redis.mget(['name', 'nickname'])

返回 name 和 nickname 对应的 value

[b’Mike', b’Miker']

setnx(name, value)

如果不存在指定的键值对,则更新键值,否则保持不变

name: 键名
value: 值

redis.setnx('newname', 'James')

如果不存在 newname 这个键名,则设置相应键值对,对应键值为 James

第一次的运行结果是 True,第二次的运行结果是 False

setex(name, time, value)

设置键名对应的键值为字符串类型,并指定此键值的有效期

name: 键名
time: 有效期
value: 值

redis.setex('name', 1, 'James')

将 name 这个键的值设置为 James,有效期设置为 1 秒

True

setrange(name, offset, value)

设置指定键名对应的键值的子字符串

name: 键名
offset: 偏移量
value: 子字符串

redis.set('name', 'Hello')
redis.setrange('name', 6, 'World')

将 name 这个键对应键值赋为字符串 Hello,并在该键值 index 为 6 的位置补充 World

11,修改后的字符串长度

mset(mapping)

批量赋值

mapping: 字典或关键字参数

redis.mset({'name1': 'Durant', 'name2': 'James'})

将 name1 赋值为 Durant,name2 赋值为 James

True

msetnx(mapping)

指定键名均不存在时,才批量赋值

mapping: 字典或关键字参数

redis.msetnx({'name3': 'Smith', 'name4': 'Curry'})

在 name3 和 name4 均不存在的情况下,才赋值

True

incr(name, amount=1)

对指定键名对应的键值做增值操作,默认增加 1。如果指定的键名不存在,则创建一个,并将键值设为 amount

name: 键名
amount: 增加的值

redis.incr('age', 1)

将 age 对应的键值增加 1。如果不存在 age 这个键名,则创建一个,并设置键值为 1

1,即修改后的值

decr(name, amount=1)

对指定键名对应的键值做减值操作,默认减 1。如果指定的键不存在,则创建一个,并将键值设置为 amount

name: 键名
amount: 减少量

redis.decr('age', 1)

将 age 对应的值减 减 1。如果不存在 age 这个键名,则创建一个,并设置键值为 1

1,即修改后的值

append(key, value)

对指定键名对应的键附加字符串 value

key: 键名
value: 值

redis.append('nickname', 'OK')

在键 nickname 对应的键值后面追加字符串 OK

13,即修改后的字符串长度

substr(name, start, end=-1)

返回指定键名对应的键值的子字符串

name: 键名
start: 起始索引
end: 终止索引,默认为 -1,表示截取到末尾

redis.substr('name', 1, 4)

返回键名 name 对应的键值的子字符串,截取键值字符串中索引为 1-4 的字符

b’ello'

getrange(key, start, end)

获取指定键名对应的键值中从 start 到 end 位置的子字符串

key: 键名
start: 起始索引
end: 终止索引

redis.getrange('name', 1, 4)

返回键名 name 对应的键值的子字符串。截取键值字符串中索引为 1-4 的字符

b’ello'

列表操作

Redis 提供了列表存储,列表内的元素可以重复,而且可以从两端存储,操作列表的方法见表4-7。

Table 3. 表 4-7 列表的操作方法
方法 作用 参数说明 实例 实例说明 实例结果

rpush(name, *values)

在键名为 name 的列表末尾添加值为 value 的元素,可以传入多个 value

name: 键名
values: 值

redis.rpush('list', 1, 2, 3)

向键名为 list 的列表末尾添加 1、2、3

3,即列表大小

lpush(name, *values)

在键名为 name 的列表头部添加值为 value 的元素,可以传入多个 value

name: 键名
values: 值

redis.lpush('list', 0)

向键名为 list 的列表头部添加 0

4,即列表大小

llen(name)

返回键名为 name 的列表的长度

name: 键名

redis.llen('list')

返回键名为 list 的列表的长度

4

lrange(name, start, end)

返回键名为 name 的列表中索引从 start 到 end 之间的元素

name: 键名
start: 起始索引
end: 终止索引

redis.lrange('list', 1, 3)

返回索引从 1 到 3 对应的列表元素

[b'3', b'2', b'1']

ltrim(name, start, end)

截取键名为 name 的列表。保留索引从 start 到 end 之间的元素

name: 键名
start: 起始索引
end: 终止索引

ltrim('list', 1, 3)

保留键名为 list 的列表中索引从 1 到 3 之间的元素

True

lindex(name, index)

返回键名为 name 的列表中 index 位置的元素

name: 键名
index: 索引

redis.lindex('list', 1)

返回键名为 list 的列表中索引为 1 的元素

b'2'

lset(name, index, value)

给键名为 name 的列表表中 index 位置的元素赋值。如果 index 越界就报错

name: 键名
index: 索引位置
value: 值

redis.lset('list', 1, 5)

将键名为 list 的列表中索引为 1 的位置赋值为 5

True

lrem(name, count, value)

删除键名为 name 的列表中 count 个值为 value 的元素

name: 键名
count: 删除个数
value: 值

redis.lrem('list', 2, 3)

删除键名为 list 的列表中的两个 3

2,即删除的个数

lpop(name)

返回并删除键名为 name 的列表中的首元素

name: 键名

redis.lpop('list')

返回并删除键名为 list 的列表中的第一个元素

b'5'

rpop(name)

返回并删除键名为 name 的列表中的尾元素

name: 键名

redis.rpop('list')

返回并删除键名为 list 的列表中的最后一个元素

b'2'

blpop(keys, timeout=0)

返回并删除指定键名对应列表中的首个元素。如果列表为空,则一直阻塞等待

keys: 键名序列
timeout: 超时等待时间,0 表示一直等待

redis.blpop('list')

返回并删除键名为 list 的列表中的第一个元素

[b'5']

brpop(keys, timeout=0)

返回并删除指定键名对应列表中的尾元素。如果列表为空,则一直阻塞等待

keys: 键名序列
timeout: 超时等待时间,0 表示一直等待

redis.brpop('list')

返回并删除键名为 list 的列表中的最后一个元素

[b'2']

rpoplpush(src, dst)

返回并删除键名为 src 的列表中的尾元素,并将该元素添加到键名为 dst 的列表的头部

src: 源列表的键名
dst: 目标列表的键名

redis.rpoplpush('list', 'list2')

删除键名为 list 的列表中的最后一个元素,并将其添加到键名为 list2 的列表的头部,然后返回

b'2'

集合操作

Redis 还提供了集合存储,集合中的元素都是不重复的,操作集合的方法见表 4-8。

Table 4. 表 4-8 集合的操作方法
方法 作用 参数说明 实例 实例说明 实例结果

sadd(name, *values)

向键名为 name 的集合中添加元素

name: 键名
values: 值,可以为多个

redis.sadd('tags', 'Book', 'Tea', 'Coffee')

向键名为 tags 的集合中添加 Book, Tea 和 Coffee 这 3 项内容

3,即添加的数据个数

srem(name, *values)

从键名为 name 的集合中删除元素

name: 键名
values: 值,可以为多个

redis.srem('tags', 'Book')

从键名为 tags 的集合中删除 Book

1,即删除的数据个数

spop(name)

随机返回并删除键名为 name 的集合中的一个元素

name: 键名

redis.spop('tags')

随机删除并返回键名为 tags 的集合中的某个元素

b’Tea'

smove(src, dst, value)

从键名为 src 的集合中移除 value,并将其添加到 dst 对应的集合中

src: 源集合
dst: 目标集合
value: 元素值

redis.smove('tags', 'tags2', 'Coffee')

从键名为 tags 的集合中删除元素 Coffee,并将其添加到键名为 tags2 的集合中

True

scard(name)

返回键名为 name 的集合中的元素数

name: 键名

redis.scard('tags')

获取键名为 tags 的集合中的元素数

3

sismember(name, value)

测试 member 是否是键名为 name 的集合中的元素

name: 键值
member: 元素

redis.sismember('tags', 'Book')

判断 Book 是否是键名为 tags 的集合中的元素

True

sinter(keys, *args)

返回所有给定键的集合的交集

keys: 键名序列

redis.sinter(['tags', 'tags2'])

返回键名为 tags 的集合和键名为 tags2 的集合的交集

{b’Coffee'}

sinterstore(dest, keys, *args)

求多个集合的交集,并将交集保存到键名为 dest 的集合

dest: 结果集合
keys: 键名序列

redis.sinterstore('inttag', ['tags', 'tags2'])

求键名为 tags 的集合和键名为 tags2 的集合的交集,并将其保存为键名为 inttag 的集合

1

sunion(keys, *args)

返回所有给定键的集合的并集

keys: 键名序列

redis.sunion(['tags', 'tags2'])

返回键名为 tags 的集合和键名为 tags2 的集合的并集

b’Coffee', b’Book', b’Pen'

sunionstore(dest, keys, *args)

求多个集合的并集,并将并集保存到键名为 dest 的集合

dest: 结果集合
keys: 键名序列

redis.sunionstore('inttag', ['tags', 'tags2'])

求键名为 tags 的集合和键名为 tags2 的集合的并集,并将其保存为键名为 inttag 的集合

3

sdiff(keys, *args)

返回所有给定键的集合的差集

keys: 键名序列

redis.sdiff(['tags', 'tags2'])

返回键名为 tags 的集合和键名为 tags2 的集合的差集

{b’Coffee', b’Book', b’Pen'}

sdiffstore(dest, keys, *args)

求多个集合的差集,并将差集保存到键名为 dest 的集合

dest: 结果集合
keys: 键名序列

redis.sdiffstore('inttag', ['tags', 'tags2'])

求键名为 tags 的集合和键名为 tags2 的集合的差集,并将其保存为键名为 inttag 的集合

3

smembers(name)

返回键名为 name 的集合中的所有元素

name: 键名

redis.smembers('tags')

返回键名为 tags 的集合中的所有元素

{b’Pen', b’Book', b’Coffee'}

srandmember(name)

随机返回键名为 name 的集合中的一个元素,但不删除该元素

name: 键值

redis.srandmember('tags')

随机返回键名为 tags 的集合中的一个元素

Srandmember(name)

有序集合操作

有序集合比集合多了一个分数字段,利用该字段可以对集合中的数据进行排序,操作有序集合的方法总结见表 4-9。

Table 5. 表 4-9 有序集合的操作方法
方法 作用 参数说明 实例 实例说明 实例结果

zadd(name, args, **kwargs)

向键名为 name 的有序集合中添加元素。score 字段用于数排序,如果该元素存在,则更新该元素的顺序

name: 键名
args: 可变参
score: 字典用于数

redis.zadd('grade', 100, 'Bob', 98, 'Mike')

向键名为 grade 的有序集合中添加 Bob (对应 score 为 100) 和 Mike (对应 score 为 98)

2,即添加的元素个数

zrem(name, *values)

删除键名为 name 的有序集合中的元素

name: 键名
values: 元素

redis.zrem('grade', 'Mike')

从键名为 grade 的有序集合中删除 Mike

1,即删除的元素个数

zincrby(name, value, amount=1)

如果键名为 name 的有序集合中已存在 value 元素,则将该元素的 score 增加 amount;否则向集合中添加 value 元素,其 score 的值为 amount

name: 键名
value: 元素
amount: 增长的 score 值

redis.zincrby('grade', 'Bob', -2)

将键名为 grade 的有序集合中 Bob 元素的 score 减 2

98.0,即修改后的值

zrank(name, value)

返回键名为 name 的有序集合中 value 元素的排名,或名次 (对各元素按照 score 从小到大排序)

name: 键名
value: 元素

redis.zrank('grade', 'Amy')

得到键名为 grade 的有序集合中 Amy 的排名

1

zrevrank(name, value)

返回键名为 name 的有序集合中 value 元素的倒数排名,或名次 (对各元素按照 score 从大到小排序)

name: 键名
value: 元素

redis.zrevrank('grade', 'Amy')

得到键名为 grade 的有序集合中 Amy 的倒数排名

2

zrevrange(name, start, end, withscores=False)

返回键名为 name 的有序集合中名次从 start 到 end 之间的所有元素 (按照 score 从大到小排序)

name: 键名
start: 开始索引
end: 结束索引
withscores: 是否带 score

redis.zrevrange('grade', 0, 3)

返回键名为 grade 的有序集合中的前 4 名元素

[b’Bob', b’Mike', b’Amy', b’James']

zrangebyscore(name, min, max, start=None, num=None, withscores=False)

返回键名为 name 的有序集合在给定区间中的元素

name: 键名
min: 最低 score
max: 最高 score
start: 起始索引
num: 个数
withscores: 是否带 score

redis.zrangebyscore('grade', 80, 95)

返回键名为 grade 的有序集合中 score 在 80 和 95 之间的元素

[b’Bob', b’Mike', b’Amy', b’James']

zcount(name, min, max)

返回键名为 name 的有序集合中 score 在给定区间内的元素数量

name: 键名
min: 最低 score
max: 最高 score

redis.zcount('grade', 80, 95)

返回键名为 grade 的有序集合中 score 在 80 和 95 之间的元素个数

4

zcard(name)

返回键名为 name 的有序集合中的元素个数

name: 键名

redis.zcard('grade')

获取键名为 grade 的有序集合中元素的个数

3

zremrangebyrank(name, min, max)

删除键名为 name 的有序集合中排名在给定区间内的元素

name: 键名
min: 最低名次
max: 最高名次

redis.zremrangebyrank('grade', 0, 0)

删除键名为 grade 的有序集合中排名第一的元素

1,即删除的元素个数

zremrangebyscore(name, min, max)

删除键名为 name 的有序集合中 score 在给定区间内的元素

name: 键名
min: 最低 score
max: 最高 score

redis.zremrangebyscore('grade', 80, 90)

删除键名为 grade 的有序集合中 score 在 80 和 90 之间的元素

1,即删除的元素个数

散列操作

Redis 还提供了散列表这种数据结构,我们可以用 name 指定一个散列表的名称,表内存储着多个键值对,操作散列表的方法总结见表 4-10。

Table 6. 表 4-10 散列表的操作方法
方法 作用 参数说明 实例 实例说明 实例结果

hset(name, key, value)

向键名为 name 的散列表中添加映射

name: 散列表键名
key: 映射名
value: 映射键值

redis.hset('price', 'cake', 5)

向键名为 price 的散列表中添加映射关系,cake 的值为 5

1, 即添加的映射个数 1

hsetnx(name, key, value)

如果键名为 name 的散列表中不存在 key 映射,则给其添加此映射

name: 散列表键名
key: 映射名
value: 映射键值

redis.hsetnx('price', 'book', 6)

向键名为 price 的散列表中添加映射关系,book 的值为 6

1, 即添加的映射个数 1

hget(name, key)

返回键名为 name 的散列表中 key 对应的键值

name: 散列表键名
key: 映射名

redis.hget('price', 'cake')

获取键名为 price 的散列表中键名 cake 对应的值

b'5'

hmget(name, keys, *args)

返回键名为 name 的散列表中各个键名对应的值

name: 散列表键名
keys: 键名序列

redis.hmget('price', ['apple', 'orange'])

获取键名为 price 的散列表中 b'3' 和 b'7' 对应的键值

[b'3', b'7']

hmset(name, mapping)

向键名为 name 的散列表中批量添加映射

name: 散列表键名
mapping: 映射字典

redis.hmset('price', {'banana': 2, 'pear': 6})

向键名为 price 的散列表中批量添加映射

True

hincrby(name, key, amount=1)

将键名为 name 的散列表中的映射键值增加 amount

name: 散列表键名
key: 映射键名
amount: 增长量

redis.hincrby('price', 'apple', 3)

将键名为 price 的散列表中 apple 的键值增加 3

6,修改后的值

hexists(name, key)

返回键名为 name 的散列表中是否存在键名为 key 的映射

name: 散列表键名
key: 映射键名

redis.hexists('price', 'banana')

返回键名为 price 的散列表中是否存在键名为 banana 的映射

True

hdel(name, *keys)

在键名为 name 的散列表中,删除键名有给定键的映射

name: 散列表键名
keys: 映射键序列

redis.hdel('price', 'banana')

从键名为 price 的散列表中,删除键名为 banana 的映射

True

hlen(name)

获取键名为 name 的散列表中有多少个映射

name: 散列表键名

redis.hlen('price')

获取键名为 price 的散列表中的映射个数

6

hkeys(name)

获取键名为 name 的散列表中所有映射键名

name: 散列表键名

redis.hkeys('price')

获取键名为 price 的散列表中所有映射键名

[b’cake', b’book', b’banana', b’pear']

hvals(name)

获取键名为 name 的散列表中所有映射键值

name: 散列表键名

redis.hvals('price')

获取键名为 price 的散列表中所有映射键值

[b'5', b'6', b'2', b'6']

hgetall(name)

获取键名为 name 的散列表中所有键值对

name: 散列表键名

redis.hgetall('price')

获取键名为 price 的散列表中所有映射键值对

{b’cake': b'5', b’book': b'6', b’orange': b'7', b’pear': b'6'}

总结

鉴于 Redis 的便捷性和高效性,后面我们会利用 Redis 实现很多架构,例如维护代理池、账号池、ADSL 拨号代理池、Scrapy-Redis 分布式架构等,所以需要好好掌握针对 Redis 的操作。