背景

由于Redis的单线程服务模式,命令keys *会阻塞正常的业务请求,不建议使用keys * pattern的方法进行查询,可能会使服务器卡顿而出现事故。如何获取指定的 key?

可以采用Redis提供的SCAN命令。SCAN 命令是一个基于游标的迭代器(cursor based iterator):SCAN 命令每次被调用之后都会向用户返回一个新的游标, 用户在下次迭代时会使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代;而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。

SCAN的语法如下:

SCAN cursor [MATCH pattern] [COUNT count]

其中 cousor 是游标,MATCH 则支持正则匹配,我们正好可以利用此功能,比如匹配 前缀为"dba_"的key, COUNT 是每次获取多少个key。

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 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集(collection)已经被完整遍历过了。

从上面的示例可以看到, SCAN 命令的回复是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素。

例如:  SCAN    0   MATCH  aaa*   COUNT    5  #表示从游标0开始查询aaa开头的key,每次返回5条,但是这个5条不一定,只是给Redis了参考值,具体返回数量看Redis。

注意:以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历(full iteration)。 我们会在后面的代码实现中利用此特点。

Python的redis 模块提供 scan_iter 迭代器来遍历key,其返回的结果迭代器对象。

    In [53]: ret=r.scan_iter('dba_*',20)
In [54]: print ret
<generator object scan_iter at 0x102ff45a0>

至此,我们解决了如何获取数据的问题,下面思考第二个问题。

如何执行删除

这个相对比较简单,Redis 提供DEL 命令

    127.0.0.1:6379[2]> get "dba_7"
"r06cVX9"
127.0.0.1:6379[2]> get "dba_1"
"ETX57PA"
127.0.0.1:6379[2]> del "dba_7" "dba_1"
(integer) 2
127.0.0.1:6379[2]>

在redis-py 中,提供了delete(key),delete(*key)的函数, 其中参数 *key 是多个值的列表。 到这里,我们大致可以想到获取key,然后批量删除

(mytest)➜  test git:(master) ✗ python delete_key.py
initial keys successfully,use time: 90.2497739792
normal ways end at: 68.685477972
normal ways delete numbers: 1000000

常规方式的删除10W个key耗时68.7秒,如果是1.2亿个key 要多少时间呢?68*1000/3600=18.8小时。能不能更快呢?

如何提高执行速度

Redis本身是基于Request/Response协议的,客户端发送一个命令,等待Redis应答,Redis在接收到命令,处理后应答。其中发送命令加上返回结果的时间称为(Round Time Trip)RRT-往返时间。如果客户端发送大量的命令给Redis,那就是等待上一条命令应答后再执行再执行下一条命令,这中间不仅仅多了RTT,而且还频繁的调用系统IO,发送网络请求。

Pipeline(流水线)功能极大的改善了上面的缺点。Pipeline能将一组Redis命令进行组装,然后一次性传输给Redis,再将Redis执行这组命令的结果按照顺序返回给客户端。

需要注意的是Pipeline 虽然好用,但是Pipline组装的命令个数不能没有限制,否则一次组装数据量过大,一方面增加客户端的等待时间,另一方面会造成网络阻塞,需要批量组装。使用Pepline 和常规方式的性能对比如下:

代码

import redis
import random
import string
import time
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=2)
r = redis.Redis(connection_pool=pool) def random_str():
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(7)) def init_keys():
start_time = time.time()
for i in xrange(0, 20):
key_name = 'dba_'+str(i)
value_name = random_str()
r.set(key_name, value_name)
print 'initial keys successfully,use time:', time.time() - start_time def del_keys_without_pipe():
start_time = time.time()
result_length = 0
for key in r.scan_iter(match='dba_*', count=2000):
r.delete(key)
result_length += 1
print "normal ways end at:", time.time() - start_time
print "normal ways delete numbers:", result_length def del_keys_with_pipe():
start_time = time.time()
result_length = 0
pipe = r.pipeline()
for key in r.scan_iter(match='dba_*', count=5000):
pipe.delete(key)
result_length += 1
if result_length % 5000 == 0:
pipe.execute()
pip_time = time.time()
print "use pipeline scan time ", time.time() - start_time
pipe.execute() print "use pipeline end at:", time.time() - pip_time
print "use pipeline ways delete numbers:", result_length def main():
init_keys()
del_keys_without_pipe()
init_keys()
del_keys_with_pipe() if __name__ == '__main__':
main()

Redis如何找出并快速删除亿级指定前缀的key的更多相关文章

  1. redis key全量导出与导出指定前缀的key

    redis命令列表中有两种方法可以全量导出所有的key: (1)keys 由于redis是单线程的,使用keys会导致redis服务阻塞,不建议线上服务采用这种方式. (2)scan 命令,下面是使用 ...

  2. php批量删除数据库下指定前缀的表

    如何用php批量删除数据库下所有前缀为prefix_的表. 例子,统一删除前缀为“prefix_”的表. <?php //设置数据库连接信息.数据库服务器地址,数据库用户名,数据密码 mysql ...

  3. Python 脚本帮你找出微信上删除了你的“好友“

  4. Redis系列9:Geo 类型赋能亿级地图位置计算

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...

  5. laravel redis 删除指定前缀的 key

    // 前缀 $prefix = 'abc'; // 需要在前面连接上应用的缓存前缀 $keys = app('redis')->keys(config('cache.prefix') . $pr ...

  6. 我用 Python 找出了删除我微信的所有人并将他们自动化删除了

    1. 概述 不知你是否遇到过在微信上给通讯录中的某个人发消息,结果出现了这一幕: 平时一直认为自己的心里素质过硬,不过遇到这种情况 ... 在我缓了半个钟头(半分钟)之后,缓缓拿出了手机,打开微信,找 ...

  7. Redis实战:如何构建类微博的亿级社交平台

    微博及 Twitter 这两大社交平台都重度依赖 Redis 来承载海量用户访问.本文介绍如何使用 Redis 来设计一个社交系统,以及如何扩展 Redis 让其能够承载上亿用户的访问规模. 虽然单台 ...

  8. Redis删除相同前缀的key

          如何优雅地删除Redis set集合中前缀相同的key?       Redis中有删除单条数据的命令DEL,却没有批量删除特定前缀key的指令,但我们经常遇到需要根据前缀来删除的业务场景 ...

  9. redis删除指定前缀的缓存

    redis作为缓存服务器为MySQL数据库提供较高的防御性,对于一些数据的查询可以直接从缓存中可以进行查询. 但是,某些情况下,我们需要清除缓存. 以下场景: 公司经常做活动,每个活动都存在大量的数据 ...

  10. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

随机推荐

  1. iOS开发之UIImage在压缩时失真问题,压缩图片的大小

    今天遇到UIImage在压缩时失真问题,压缩图片的大小图片模糊 错误的方案 /** * 压缩图片 * image:将要压缩的图片 size:压缩后的尺寸 */ -(UIImage*) OriginIm ...

  2. FlyMcu烧录提示写入出错在初始化:连接,耗时3437延秒

    取消勾选RamIsp的选项就可以了

  3. 25 String 对象中的属性

    <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF ...

  4. Linux 第七节( 磁盘配额,RAID )

    vim /etc/fstab    //编辑文件 UUID=xxxxxxxxxxxxxx /boot xfs defaults,uquota 0 0    //增加uqota命令,允许磁盘扩容 ino ...

  5. 路飞项目 day02 全局日志、全局异常处理、封装Response、数据库准备

    一.路飞项目全局日志配置 ​ 那个代码不用死记硬背,知道一些地方是啥意思即可 1.复制django自带的日志模块的大代码到dev(settings)文件中 LOGGING = { 'version': ...

  6. docker登录Ubuntu出现error storing credentials - err: exit status 1, out: `Cannot autolaunch D-Bus without X11 $DISPLAY`的解决方法

    命令行登录docker时,在Ubuntu 18.04下可能会出现 error storing credentials - err: exit status 1, out: `Cannot autola ...

  7. pritunl zero 零信任系统

    pritunl zero 零信任系统 一.概述 1.介绍 Pritunl Zero是一个零信任系统,它可以在不使用VPN的情况下从不受信任的网络安全地访问内部服务. 2.背景 内网搭建了类百度文库系统 ...

  8. ts的接口和泛型的基本语法

    一.接口 1.接口定义 接口是一种规范的定义,它定义行为和规范,在程序设计中接口起到限制和规范的作用. 2.接口的声明与使用 //声明对象类型接口 interface Person {   name: ...

  9. 提取可执行文件中的调试信息 objcopy --only-keep-debug app app.debug

    https://blog.csdn.net/CaspianSea/article/details/17269977 set-debug-directory show-debug-directory i ...

  10. C#中延迟初始化实现原理的一点浅见。

    定义 延迟初始化:一个对象的延迟初始化意味着它的创建被推迟到它第一次使用.(对于本主题,延迟初始化和延迟实例化是同义词.)延迟初始化主要用于提高性能.避免浪费计算和减少程序内存需求. 用法及简单介绍 ...