python使用redis_python应用中使用redis的几个思考
使用pipeline來提高性能
應(yīng)該使用pipeline來將多個請求組合在一起,一次性在發(fā)送給服務(wù)器,并返回結(jié)果。
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()
上述代碼執(zhí)行后, #1, #2, #3的結(jié)果依次保存在數(shù)組result中。如果不使用pipeline,上述#1, #2, #3將分為三次網(wǎng)絡(luò)傳輸完成,占用3個RTT的時間,使用pipeline將使得這一時間減少到1個RTT時間。
注意pipeline只是將多個命令組合在一起發(fā)送給服務(wù)器,且結(jié)果也一次性返回,但并不意味著這些命令的執(zhí)行是原子性的。要保證其原子性,應(yīng)該使用事務(wù)。
pipeline也有美中不足的地方,就是你沒有辦法利用中間結(jié)果進行運算。比如如果#2命令依賴于#1的輸出結(jié)果,那么這兩個操作是無法pipeline在一起的。這時應(yīng)該使用客戶端腳本。
使用迭代和batch操作來提高性能
如果你要一次性地操作比如說1B的數(shù)據(jù),那么很有可能你不會有這么多內(nèi)存來存儲返回的keys,這種情況下,應(yīng)該使用scan操作。比如我們要刪除redis中的所有以user:開頭的key:
import redis
r = redis.StrictRedis(host='localhost')
for key in r.scan_iter("user:*"):
r.delete(key)
這樣內(nèi)存問題解決了,但是有點慢。
如果要一次操作很多數(shù)據(jù),比如說100k以上,應(yīng)該使用批量操作:
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,根據(jù)作者的測試,使用批量操作(且batch size為500),最高將提高5倍的速度。
需要使用asyncio嗎?
由于python的執(zhí)行是單線程的,所以在python中,一旦涉及到IO操作,我們都盡可能地使用asyncio來完成。但在訪問redis時,是否要使用asyncio,要具體分析。一般來說,我們會把redis服務(wù)器部署在離客戶端很近的地方,甚至可能就在本機,由于RTT很小,而redis本身性能很高,延時很低,因此完全可以不用異步編程模型。畢竟對redis的使用會很頻繁,python的asyncio模型中加入這么多請求后,對其調(diào)度性能也是一種考驗。兩相折沖,使用asyncio的性能并不一定高。也許這也是python下沒有特別好的asyncio模型的redis客戶端的原因。但是,上述結(jié)論是基于我們使用redis的場景是高頻,小數(shù)據(jù)量的情況。如果要與redis交換大量數(shù)據(jù),顯然還是要使用異步,以免因與redis的通信阻塞程序運行。關(guān)于這一點,我并沒有做測試,也沒有找到合適的benchmark文章。不過看到知乎文章aredis —— 一款高效的異步 redis 客戶端也有類似的觀點。
如何存儲復(fù)雜的數(shù)據(jù)結(jié)構(gòu)?
redis通過set/get, hset/hmset/hmget等命令提供了嵌套級別為0~1級的各種操作。但有時候我們需要讀寫更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),我們應(yīng)該如何拓展信息嵌套的層次呢?
首先,可以使用多個數(shù)據(jù)庫來提供第一層的嵌套(或者第一級的名字空間)。這是通過建立連接時指定db索引來實現(xiàn)的:
import redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)
# 通過r發(fā)生的操作都寫在db=0這個數(shù)據(jù)庫中。
redis數(shù)據(jù)庫通過整數(shù)索引,而不是更易理解的字符串名字來標識。因此,在正式的工程中,你應(yīng)該定義一些常量,通過常量名來區(qū)分各個數(shù)據(jù)庫的作用。不同的數(shù)據(jù)庫是允許存在相同的key的。現(xiàn)在,假設(shè)我們有一個CRM系統(tǒng),則相應(yīng)的用戶數(shù)據(jù),我們可以分別保存在vendor(供貨商)和customer(客戶)數(shù)據(jù)庫下。
redis提供了hash, list, set這樣的容器,從而提供了另一個級別的嵌套。比如我們的客戶數(shù)據(jù)可能組成如下:
id -> {
name: 'john',
mobile: 13400001234,
orders: [oid1, oid2, ...]
}
這樣我們可以直接通過hmset來更改用戶的名字、電話等數(shù)據(jù)。如果要操作他的訂單數(shù)據(jù)呢?當然我們可以這樣:
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增加一筆訂單,沒有必要把之前的訂單數(shù)據(jù)都取回來(特別是如果用戶訂單數(shù)據(jù)很長的話,那就更是浪費)。但是redis并不提供對這一嵌套級別數(shù)據(jù)的直接操作。
有兩個辦法來解決這一問題(如何更高效地修改訂單數(shù)據(jù))。一是使用服務(wù)器腳本。或者,我們在key上做文章。比如對每一級嵌套的容器類型(hash, list, set),我們通過給它一個多級的key來將其從內(nèi)部嵌套中提取出來。以上面的用戶訂單數(shù)據(jù)為例,它是一個兩層的嵌套,超過了redis命令可以直接運算的最大深度,因此我們可以這樣改造存儲在redis中的數(shù)據(jù):
id -> {
name: 'john',
mobile: 13400001234
...
}
id:orders -> [oid1, oid2, oid3, ...]
# 還可以通過類似的方法將更深層的數(shù)據(jù)“扁平化”
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的python使用redis_python应用中使用redis的几个思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python数据可视化的特点_Pytho
- 下一篇: python列表功能默写_Python