Redis中Scan命令的踩坑实录_Redis

来源:脚本之家  责任编辑:小易  

redis是很纯粹的no sql,没办法。必须得条件查询的话,推荐是将要作为条件的列的值都拼接到redis的key中,然后使用程序+规则定制,使用程序拼出所有可能的主键,然后进行查询筛选,或者进行scan遍历。效率不高,没什么意义,还不如直接使用mysqlwww.zgxue.com防采集请勿采集本网。

1、原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作。但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限。所以记录下这个踩坑的过程,背景如下:

1、参数parentNodeId,sonNodeName;2、dao创建子节点,返回子节点sonNodeId;3、存父子关系(1:N),redisDao.save(parentNodeId,sonNodeId);4、dao保存页面中的元素,返回idList;5、存子节点对应

公司因为redis服务器内存吃紧,需要删除一些无用的没有设置过期时间的key。大概有500多w的key。虽然key的数目听起来挺吓人。但是自己玩redis也有年头了,这种事还不是手到擒来?

实际上开发一个专用工具非常简单,利用SCAN和DEBUG等命令,没多少行代码就能实现: php patterns=array 'foo:.+', 'bar:.+', '.+', redis=new Redis();redis->setOption(Redis:OPT_SCAN,Redis:SCAN_RETRY

当时想了下,具体方案是通过lua脚本来过滤出500w的key。然后进行删除动作。lua脚本在redis server上执行,执行速度快,执行一批只需要和redis server建立一次连接。筛选出来key,然后一次删1w。然后通过shell脚本循环个500次就能删完所有的。以前通过lua脚本做过类似批量更新的操作,3w一次也是秒级的。基本不会造成redis的阻塞。这样算起来,10分钟就能搞定500w的key。

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

然后,我就开始直接写lua脚本。首先是筛选。

虎躯一震

用过redis的人,肯定知道redis是单线程作业的,肯定不能用keys命令来筛选,因为keys命令会一次性进行全盘搜索,会造成redis的阻塞,从而会影响正常业务的命令执行。

作为一个替代命令,看一下 SCAN 吧,其允许你以一种更友好的方式来执行…SCAN 通过增量迭代的方式来扫描数据库。这一操作基于游标的迭代器来完成的,因此只要你觉得合适,你可以随时停止或继续。

500w数据量的key,只能增量迭代来进行。redis提供了scan命令,就是用于增量迭代的。这个命令可以每次返回少量的元素,所以这个命令十分适合用来处理大的数据集的迭代,可以用于生产环境。

scan命令会返回一个数组,第一项为游标的位置,第二项是key的列表。如果游标到达了末尾,第一项会返回0。

2、所以我写的第一版的lua脚本如下:

local c = 0local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000)c = tonumber(resp[1])local dataList = resp[2]for i=1,#dataList do local d = dataList[i] local ttl = redis.call('TTL',d) if ttl == -1 then redis.call('DEL',d) endendif c==0 then return 'all finished'else return 'end'end

在本地的测试redis环境中,通过执行以下命令mock了20w的测试数据:

eval "for i = 1, 200000 do redis.call('SET','authToken_' .. i,i) end" 0

然后执行script load命令上传lua脚本得到SHA值,然后执行evalsha去执行得到的SHA值来运行。具体过程如下:

我每删1w数据,执行下dbsize(因为这是我本地的redis,里面只有mock的数据,dbsize也就等同于这个前缀key的数量了)。

奇怪的是,前面几行都是正常的。但是到了第三次的时候,dbsize变成了16999,多删了1个,我也没太在意,但是最后在dbsize还剩下124204个的时候,数量就不动了。之后无论再执行多少遍,数量还依旧是124204个。

随即我直接运行scan命令:

发现游标虽然没有到达末尾,但是key的列表却是空的。

这个结果让我懵逼了一段时间。我仔细检查了lua脚本,没有问题啊。难道是redis的scan命令有bug?难道我理解的有问题?

我再去翻看redis的命令文档对count选项的解释:

经过详细研读,发现count选项所指定的返回数量还不是一定的,虽然知道可能是count的问题,但无奈文档的解释实在难以很通俗的理解,依旧不知道具体问题在哪

3、后来经过某个小伙伴的提示,看到了另外一篇对于scan命令count选项通俗的解释:

看完之后恍然大悟。原来count选项后面跟的数字并不是意味着每次返回的元素数量,而是scan命令每次遍历字典槽的数量

我scan执行的时候每一次都是从游标0的位置开始遍历,而并不是每一个字典槽里都存放着我所需要筛选的数据,这就造成了我最后的一个现象:虽然我count后面跟的是10000,但是实际redis从开头往下遍历了10000个字典槽后,发现没有数据槽存放着我所需要的数据。所以我最后的dbsize数量永远停留在了124204个。

所以在使用scan命令的时候,如果需要迭代的遍历,需要每次调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。

至此,心中的疑惑就此解开,改了一版lua:

local c = tonumber(ARGV[1])local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000)c = tonumber(resp[1])local dataList = resp[2]for i=1,#dataList do local d = dataList[i] local ttl = redis.call('TTL',d) if ttl == -1 then redis.call('DEL',d) endendreturn c

在本地上传后执行:

可以看到,scan命令没法完全保证每次筛选的数量完全等同于给定的count,但是整个迭代却很好的延续下去了。最后也得到了游标返回0,也就是到了末尾。至此,测试数据20w被全部删完。

这段lua只要在套上shell进行循环就可以直接在生产上跑了。经过估算大概在12分钟左右能删除掉500w的数据。

知其然,知其所以然。虽然scan命令以前也曾玩过。但是的确不知道其中的细节。况且文档的翻译也不是那么的准确,以至于自己在面对错误的结果时整整浪费了近1个多小时的时间。记录下来,加深理解。

总结

到此这篇关于Redis中Scan命令踩坑的文章就介绍到这了,更多相关Redis Scan命令踩坑内容请搜索真格学网以前的文章或继续浏览下面的相关文章希望大家以后多多支持真格学网!

SCAN 命令及其相关的 SSCAN 命令、HSCAN 命令和 ZSCAN 命令都用于增量地迭代(incrementally iterate)一集元素(a collection of elements):SCAN 命令用于迭代当前数据库中的数据库键。SSCAN 命令用于迭代集合键中的元素。HSCAN 命令用于迭代哈希键中的键值对。ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • redis中scan命令的深入讲解
  • php redis扩展支持scan命令实现方法
  • redis 用scan指令 代替keys指令(详解)
  • redis中scan命令的基本使用教程
  • 详解redis scan命令实现有限保证的原理
  • redis scan命令的基本使用方法
  • 利用redis实现sql伸缩的方法简介
  • redis分布式锁的正确实现方法总结
  • redis中热key问题该如何解决
  • jedis配置含义详解
  • redis数据的两种持久化方式对比
  • linux下redis的安装与使用图文教程
  • redis数据结构之intset的实例详解
  • redis 5.05 单独模式安装及配置方法
  • 利用redis的有序集合实现排行榜功能实例代码
  • redis实现分布式队列浅析
  • redis集群 可以用scan吗
  • 在redis中获取数据问题! 谢谢~~~~~
  • redis日常维护有哪些工作
  • 如何优雅地删除Redis大键
  • spring-data-redis 的hash scan 方法怎么没有传入cursor的参数
  • 如何统计Redis中各种数据的大小
  • 为什么要选择使用 Redis
  • redis hkeys和hvals 哪个性能好
  • 如何正确的使用Redis
  • 如何在Redis里按模式删除数据
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全mssqlmysqlmariadboracledb2mssql2008mssql2005sqlitepostgresqlmongodbredisaccess数据库文摘数据库其它首页redis中scan命令的深入讲解php redis扩展支持scan命令实现方法redis 用scan指令 代替keys指令(详解)redis中scan命令的基本使用教程详解redis scan命令实现有限保证的原理redis scan命令的基本使用方法利用redis实现sql伸缩的方法简介redis分布式锁的正确实现方法总结redis中热key问题该如何解决jedis配置含义详解redis数据的两种持久化方式对比linux下redis的安装与使用图文教程redis数据结构之intset的实例详解redis 5.05 单独模式安装及配置方法利用redis的有序集合实现排行榜功能实例代码redis实现分布式队列浅析超强、超详细redis数据库入门教程redis常用命令、常见错误、配置技redis操作命令总结redis中5种数据结构的使用场景介64位windows下安装redis教程redis中使用redis-dump导出、导入redis中统计各种数据大小的方法redis常用命令小结让redis在你的系统中发挥更大作用centos 6.6下redis安装配置记录redis实现布隆过滤器的方法及原理redis高级玩法之利用sortedset实现多维度windows下redis安装配置教程利用redis如何实现自动补全功能linux下安装redis并设置相关服务深入理解redis分布式锁和消息队列centos 6.5 64位下安装redis3.0.2的具体步mac下设置redis开机启动方法步骤在redhat6.4安装redis集群【教程】windows 64位下redis安装教程
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved