有一篇关于 Redis 命令获取所有可用密钥的帖子,但我想用 Python 来完成。
有什么办法可以做到这一点吗?
使用
scan_iter()
对于大量键,scan_iter()
优于 keys()
,因为它为您提供了一个可以使用的迭代器,而不是尝试将所有键加载到内存中。
我的 Redis 中有 1B 条记录,但我永远无法获得足够的内存来一次返回所有键。
一一扫描按键
这里是一个 python 片段,使用
scan_iter()
从存储中获取与模式匹配的所有键并将它们一一删除:
import redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)
for key in r.scan_iter("user:*"):
# delete the key
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)
我对这个脚本进行了基准测试,发现使用 500 的批量大小比逐一扫描密钥快 5 倍。我测试了不同的批量大小(3,50,500,1000,5000),发现批量大小为 500 似乎是最佳的。
请注意,无论您使用
scan_iter()
还是 keys()
方法,该操作都不是原子操作,可能会中途失败。
绝对避免在命令行上使用 XARGS
我不推荐这个我在其他地方重复出现的例子。它对于 unicode 键会失败,并且对于中等数量的键也非常慢:
redis-cli --raw keys "user:*"| xargs redis-cli del
在此示例中,xargs 为每个键创建一个新的 redis-cli 进程!这很糟糕。
我对这种方法进行了基准测试,它比第一个 Python 示例慢 4 倍,在第一个 Python 示例中,它逐一删除每个键,比批量删除 500 个键慢 20 倍。
keys()
:
>>> import redis
>>> r = redis.StrictRedis(host=YOUR_HOST, port=YOUR_PORT, db=YOUR_DB)
>>> r.keys()
给出一个空模式将获取所有它们。根据链接页面:
按键(模式='*')
返回匹配模式的键列表
import redis
r = redis.Redis("localhost", 6379)
for key in r.scan_iter():
print key
使用 Pyredis 库
自 2.8.0 起可用。
时间复杂度:每次调用都是 O(1)。 O(N) 完成一次迭代,包括足够的命令调用以使光标返回到 0。N 是集合内元素的数量..
对上面已接受的答案的补充。
scan_iter
可以与 count
参数一起使用,以便告诉 Redis 在单次迭代期间搜索多个键。这可以显着加快密钥获取速度,尤其是与匹配模式和大密钥空间一起使用时。
使用非常高的计数值时要小心,因为这可能会破坏其他并发查询的性能。
https://docs.keydb.dev/blog/2020/08/10/blog-post/这是一篇包含更多详细信息和一些基准的文章。
我想添加一些示例代码来配合帕特里克的答案和其他人的答案。
这显示了使用键和 scan_iter 技术的结果。
请注意,Python3 使用 zip_longest 而不是 izip_longest。下面的代码循环遍历所有键并显示它们。我将批量大小作为变量设置为 12,以使输出更小。
我写这篇文章是为了更好地理解密钥批处理是如何工作的。
import redis
from itertools import zip_longest
\# connection/building of my redisObj omitted here
\# iterate a list in batches of size n
def batcher(iterable, n):
args = [iter(iterable)] * n
return zip_longest(*args)
result1 = redisObj.get("TestEN")
print(result1)
result2 = redisObj.get("TestES")
print(result2)
print("\n\nLoop through all keys:")
keys = redisObj.keys('*')
counter = 0
print("len(keys)=", len(keys))
for key in keys:
counter +=1
print (counter, "key=" +key, " value=" + redisObj.get(key))
print("\n\nLoop through all keys in batches (using itertools)")
\# in batches of 500 delete keys matching user:*
counter = 0
batch_counter = 0
print("Try scan_iter:")
for keybatch in batcher(redisObj.scan_iter('*'), 12):
batch_counter +=1
print(batch_counter, "keybatch=", keybatch)
for key in keybatch:
if key != None:
counter += 1
print(" ", counter, "key=" + key, " value=" + redisObj.get(key))
输出示例:
Loop through all keys:
len(keys)= 2
1 key=TestES value=Ola Mundo
2 key=TestEN value=Hello World
Loop through all keys in batches (using itertools)
Try scan_iter:
1 keybatch= ('TestES', 'TestEN', None, None, None, None, None, None, None, None, None, None)
1 key=TestES value=Ola Mundo
2 key=TestEN value=Hello World
注意redis命令是单线程的,因此执行keys()可能会阻止其他redis活动。请参阅此处的优秀帖子,其中更详细地解释了这一点:SCAN vs KEYS Performance in Redis
我改进了 Patrick 和 Neal 的代码并添加了导出到 csv 的功能:
import csv
import redis
from itertools import zip_longest
redisObj = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True)
searchStr = ""
# iterate a list in batches of size n
def batcher(iterable, n):
args = [iter(iterable)] * n
return zip_longest(*args)
with open('redis.csv', 'w', newline='') as csvfile:
fieldnames = ['key', 'value']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
print("\n\nLoop through all keys in batches (using itertools)")
counter = 0
batch_counter = 0
print("Try scan_iter:")
for keybatch in batcher(redisObj.scan_iter('*'), 500):
batch_counter +=1
#print(batch_counter, "keybatch=", keybatch)
for key in keybatch:
if key != None:
counter += 1
val = ""
if (searchStr in key):
valType = redisObj.type(key)
print(valType)
match valType:
case "string":
val = redisObj.get(key)
case "list":
valList = redisObj.lrange(key, 0, -1)
val = '\n'.join(valList)
case "set":
valList = redisObj.smembers(key)
val = '\n'.join(valList)
case "zset":
valDict = redisObj.zrange(key, 0, -1, False, True)
val = '\n'.join(['='.join(i) for i in valDict.items()])
case "hash":
valDict = redisObj.hgetall(key)
val = '\n'.join(['='.join(i) for i in valDict.items()])
case "stream":
val = ""
case _:
val = ""
print(" ", counter, "key=" + key, " value=" + val)
writer.writerow({'key': key, 'value': val})