专栏首页随心DevOps【实战】如何使用 Python 从 Redis 中删除 4000万 KEY

【实战】如何使用 Python 从 Redis 中删除 4000万 KEY

本文主要涉及 Redis 的以下两个操作和其 Python 实现,目录:

  • SCAN 命令
  • DEL 命令
  • 使用 Python SCAN
  • 使用 Python DEL
  • 成果展示

SCAN 命令

SCAN 命令及相关的 SSCAN、HSCAN 和 ZSCAN 命令都用于增量迭代(incrementally iterate)一个集合的元素(a collection of elements):

  • SCAN 用于迭代当前数据库中的数据库键
  • SSCAN 用于迭代集合键中的元素
  • HSCAN 用于迭代哈希键中的键值对
  • ZSCAN 用于迭代有序集合中的元素(包括元素分值和元素分值)

以上四列命令都支持增量迭代,每次执行都会返回少量元素,所以他们都可以用于生产环境,而不会出现像 KEYS、SMEMBERS 命令一样 -- 可能会阻塞服务器

不过,增量式迭代命令也不是没有缺点的:

举个例子,使用 SMEMBERS 命令可以返回集合键当前包含的所有元素,但是对于 SCAN 这类增量迭代命令来说,因为在堆键进行增量迭代的过程中,键可能会被改变,所以增量式迭代命令只能对被返回的元素提供有限的保证(offer limited guarantees about the returned elements)。

因为 SCAN、SSCAN、HSCAN 和 ZSCAN 命令的工作方式都非常相似,但是要记住:

  • SSCAN、HSCAN 和 ZSCAN 命令的第一个参数总是一个数据库键;
  • SCAN 命令则不需要在第一个参数提供任何数据库键 -- 因为它迭代的是当前数据库中的所有数据库键。

SCAN 命令的基本用法

SCAN 命令是一个基于游标的迭代器(cursor based iterator):

SCAN 命令每次被调用后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数,以此来延续之前的迭代过程。

当 SCAN 命令的游标参数被设置为 0 时,服务器开始一次新的迭代,而当服务器向用户返回值为 0 的游标时,表示迭代结束。

示例:

redis 127.0.0.1:6379> scan 0 
1) "17" 
2)  1) "key:12"
    2) "key:8"
    3) "key:4"
    4) "key:14"
    5) "key:16"
    6) "key:17"
    7) "key:15"
    8) "key:10"
    9) "key:3"
    10) "key:7"
    11) "key:1"
redis 127.0.0.1:6379> scan 17 
1) "0" 
2)  1) "key:5"
    2) "key:18"
    3) "key:0"
    4) "key:2"
    5) "key:19"
    6) "key:13"
    7) "key:6"
    8) "key:9"
    9) "key:11"

上面的例子中,第一次迭代用 0 作为游标,表示开始第一次迭代。

第二次迭代使用第一次迭代时返回的游标,即:17。

从示例可以看出,SCAN 命令的返回是一个两个元素的数组,第一个元素是新游标,第二个元素也是一个数组,包含有所被包含的元素。

第二次调用 SCAN 命令时,返回游标 0,这表示迭代已经结束了,整个数据集(collection)已经被完整遍历过一遍了。

这个过程被称为一次完整遍历(full iteration)。

精简一下内容,补充三点:

  1. 因为 SCAN 命令仅仅使用游标来记录迭代状态,所以在迭代过程中,如果这个数据集的元素有增减,如果是减,不保证元素不返回;如果是增,也不保证一定返回;而且在某种情况下同一个元素还可能被返回多次。所以对迭代返回的元素所执行的操作最好可以重复执行多次(类似幂等)。
  2. 增量迭代命令不保证每次迭代所返回的元素数量(没扫到嘛),但是我们可以使用 COUNT 选项对命令的行为进行一定程度的调整。COUNT 参数的默认值为 10,在迭代一个足够大的、由哈希表实现的数据库、集合键、哈希键或者有序集合键时,如果用户没有使用 MATCH 选项,那么命令返回的数量通常和 COUNT 选项指定的一样,或者多一些(?),在迭代编码为整数集合(intset:一个由整数值构成的小集合)或编码为压缩列表(ziplist:由不同值构成的一个小哈希或者一个小有序集合)时,会无视 COUNT 选项指定的值,在第一次迭代就将数据集的所有元素都返回给用户。
  3. MATCH 选项,直接看示例吧,如下示例:
redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood
(integer) 6
redis 127.0.0.1:6379> sscan myset 0 match f*
1) "0"
2)  1) "foo"
    2) "feelsgood"
    3) "foobar"

注意:对元素的模式匹配工作是在命令从数据集中取出元素之后,向客户端返回元素之前进行的,所以有可能返回空,示例:

redis 127.0.0.1:6379> scan 0 MATCH *11* 
1) "288" 
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11* 
1) "224" 
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80" 
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2)  1) "key:611"
    2) "key:711"
    3) "key:118"
    4) "key:117"
    5) "key:311"
    6) "key:112"
    7) "key:111"
    8) "key:110"
    9) "key:113"
    10) "key:211"
    11) "key:411"
    12) "key:115"
    13) "key:116"
    14) "key:114"
    15) "key:119"
    16) "key:811"
    17) "key:511"
    18) "key:11"

注意:最后一次迭代,通过 COUNT 选项指定为 1000 强制命令为本次迭代扫描更多元素,从而使返回的元素也变多了。


DEL 命令

这个比较简单,删除给定的一个或者多个 key

redis> SET name "redis"OK
redis> SET type "key-value store"OK 
redis> SET website "redis.com" OK 
redis> DEL name type website
(integer) 3


使用 Python SCAN

安装 redis-py 包

pip install redis

完整代码示例:

import redis

pool=redis.ConnectionPool(
    host='redis_hostname',
    port=6379,
    max_connections=100)
r = redis.StrictRedis(connection_pool=pool)

cursor_number, keys = r.execute_command('scan', 0, "count", 200000)

while True:
    if cursor_number == 0:
        # 结束一次完整的遍历
        break
    cursor_number, keys = r.execute_command('scan', cursor_number, "count", 200000)
    
    # do something with keys

我将需要删除的 key 存在一个文件里,有 2.2G,大概 4000W 个,下一步就是删除了


使用 Python DEL

因为文件很大,我们用到一个小技巧,分块读取

with open("/data/rediskeys") as kf:
    lines = kf.readlines(1024*1024)

调用 delete 方法时,用到一个小技巧就是『*』星号

r.delete(*taskkey_list)

我们看一下定义就清楚了:

Delete Method

放上完整代码(点击阅读原文查看源代码,Gist 托管,需要访问外国网站):

import redisimport time

pool=redis.ConnectionPool(host='redis_hostname', port=6379, max_connections=100)

r = redis.StrictRedis(connection_pool=pool)

start_time = time.time()
SUCCESS_DELETED = 0

with open("/data/rediskeys") as kf:
    while True:
        lines = kf.readlines(1024*1024)
        if not lines:
            break
        else:
            taskkey_list = [i.strip() for i in lines if i.startswith("UGC:TASKKEY")]
            
            SUCCESS_DELETED += r.delete(*taskkey_list)
        
        print SUCCESS_DELETED

end_time = time.time()
print end_time - start_time, SUCCESS_DELETED


成果展示

结束,下篇再见

本文分享自微信公众号 - 随心DevOps(heart-devops),作者:临书

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-01-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 你应该使用 Python 管理 Cron 作业

    在本教程中,您将了解 cron 作业的重要性以及为什么需要它们。你可以看一下 python-crontab,这是一个与 crontab 交互的 Python 模...

    临书
  • 三分钟使用 Python 处理 Nginx 日志

    有什么 有 14 台机器(意味着我们有14份日志) 一台可以连到这 14 太机器的机器(有 Python 2.6) 要做什么 获取 14 台机器上某时间段内...

    临书
  • 进程间通信的历史与未来

    - START - 我们都知道线程是共享内存空间的,因此不会发生所谓的通信,而进程则存在如何防止多进程同时访问数据的排他控制问题。 5 种进程间通信的方式 ? ...

    临书
  • redis keys和scan命令

    1.自1.0.0起可用。 时间复杂度: O(N),其中N为数据库中密钥的数目,假设数据库中的密钥名称和给定模式的长度有限。 返回所有匹配的键pattern。

    居士
  • 操作系统篇之Linux命令操作和redis安装以及基本使用

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    海仔
  • 『互联网架构』软件架构-redis特性和集群特性(上)(48)

    保存策略: save 900 1 save 300 10 #300 秒内容如超过 10 个 key 被修改,则发起快照保存 save 60 10000

    IT故事会
  • 来聊聊NoSql

    带着这几个问题去学,我们才能将它的衣服一件件的扒光,最后看到它的本质。不然面试的时候面试官稍微问得深入一点就凉凉了。接下来聊聊NoSql。

    贪挽懒月
  • Linux redis-Sentinel配置

    然后执行下面make命令编译安装到目录/usr/local/redis/ (放执行文件)。

    程序新视界
  • Linux redis-Sentinel配置详解

    然后执行下面make命令编译安装到目录/usr/local/redis/ (放执行文件)。

    砸漏
  • Cell | 单细胞转录组揭示肝实质细胞及NPC细胞的早期细胞谱系

    原文题目:Single-Cell Transcriptomics Reveals Early Emergence of Liver Parenchymal an...

    生信菜鸟团

扫码关注云+社区

领取腾讯云代金券