python使用redis_python应用中使用redis的几个思考

使用pipeline来提高性能

应该使用pipeline来将多个请求组合在一起,一次性在发送给服务器,并返回结果。

import redis

from redis.client import Pipeline

from typing import List

connection = redis.StrictRedis(port=16379, decode_responses=True)

pipe: Pipeline = connection.pipeline()

pipe.set(...) #1

pipe.get(...) #2

pipe.sadd(...) #3

result:List = pipe.execute()

上述代码执行后, #1, #2, #3的结果依次保存在数组result中。如果不使用pipeline,上述#1, #2, #3将分为三次网络传输完成,占用3个RTT的时间,使用pipeline将使得这一时间减少到1个RTT时间。

注意pipeline只是将多个命令组合在一起发送给服务器,且结果也一次性返回,但并不意味着这些命令的执行是原子性的。要保证其原子性,应该使用事务。

pipeline也有美中不足的地方,就是你没有办法利用中间结果进行运算。比如如果#2命令依赖于#1的输出结果,那么这两个操作是无法pipeline在一起的。这时应该使用客户端脚本。

使用迭代和batch操作来提高性能

如果你要一次性地操作比如说1B的数据,那么很有可能你不会有这么多内存来存储返回的keys,这种情况下,应该使用scan操作。比如我们要删除redis中的所有以user:开头的key:

import redis

r = redis.StrictRedis(host='localhost')

for key in r.scan_iter("user:*"):

r.delete(key)

这样内存问题解决了,但是有点慢。

如果要一次操作很多数据,比如说100k以上,应该使用批量操作:

import redis

from itertools import izip_longest

r = redis.StrictRedis(host='localhost', port=6379, db=0)

# iterate a list in batches of size n

def batcher(iterable, n):

args = [iter(iterable)] * n

return izip_longest(*args)

# in batches of 500 delete keys matching user:*

for keybatch in batcher(r.scan_iter('user:*'),500):

r.delete(*keybatch)

这段代码来自于Get all keys in Redis database with python,根据作者的测试,使用批量操作(且batch size为500),最高将提高5倍的速度。

需要使用asyncio吗?

由于python的执行是单线程的,所以在python中,一旦涉及到IO操作,我们都尽可能地使用asyncio来完成。但在访问redis时,是否要使用asyncio,要具体分析。一般来说,我们会把redis服务器部署在离客户端很近的地方,甚至可能就在本机,由于RTT很小,而redis本身性能很高,延时很低,因此完全可以不用异步编程模型。毕竟对redis的使用会很频繁,python的asyncio模型中加入这么多请求后,对其调度性能也是一种考验。两相折冲,使用asyncio的性能并不一定高。也许这也是python下没有特别好的asyncio模型的redis客户端的原因。但是,上述结论是基于我们使用redis的场景是高频,小数据量的情况。如果要与redis交换大量数据,显然还是要使用异步,以免因与redis的通信阻塞程序运行。关于这一点,我并没有做测试,也没有找到合适的benchmark文章。不过看到知乎文章aredis —— 一款高效的异步 redis 客户端也有类似的观点。

如何存储复杂的数据结构?

redis通过set/get, hset/hmset/hmget等命令提供了嵌套级别为0~1级的各种操作。但有时候我们需要读写更复杂的数据结构,我们应该如何拓展信息嵌套的层次呢?

首先,可以使用多个数据库来提供第一层的嵌套(或者第一级的名字空间)。这是通过建立连接时指定db索引来实现的:

import redis

r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 通过r发生的操作都写在db=0这个数据库中。

redis数据库通过整数索引,而不是更易理解的字符串名字来标识。因此,在正式的工程中,你应该定义一些常量,通过常量名来区分各个数据库的作用。不同的数据库是允许存在相同的key的。现在,假设我们有一个CRM系统,则相应的用户数据,我们可以分别保存在vendor(供货商)和customer(客户)数据库下。

redis提供了hash, list, set这样的容器,从而提供了另一个级别的嵌套。比如我们的客户数据可能组成如下:

id -> {

name: 'john',

mobile: 13400001234,

orders: [oid1, oid2, ...]

}

这样我们可以直接通过hmset来更改用户的名字、电话等数据。如果要操作他的订单数据呢?当然我们可以这样:

import redis

r = StrictRedis(...)

id = 123456 # id of user 'john'

orders = r.hget(id, orders)

# modify orders

orders.append('1111-1121')

r.hset(id, orders)

但这样操作的效率不高,因为我们只希望给orders增加一笔订单,没有必要把之前的订单数据都取回来(特别是如果用户订单数据很长的话,那就更是浪费)。但是redis并不提供对这一嵌套级别数据的直接操作。

有两个办法来解决这一问题(如何更高效地修改订单数据)。一是使用服务器脚本。或者,我们在key上做文章。比如对每一级嵌套的容器类型(hash, list, set),我们通过给它一个多级的key来将其从内部嵌套中提取出来。以上面的用户订单数据为例,它是一个两层的嵌套,超过了redis命令可以直接运算的最大深度,因此我们可以这样改造存储在redis中的数据:

id -> {

name: 'john',

mobile: 13400001234

...

}

id:orders -> [oid1, oid2, oid3, ...]

# 还可以通过类似的方法将更深层的数据“扁平化”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值