Redis 中 scan 命令太坑了,千万别乱用!!
作者:铂赛东\
链接:www.jianshu.com/p/8cf8aac3dc25
1
原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作。但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限。所以记录下这个踩坑的过程,背景如下:
公司因为redis服务器内存吃紧,需要删除一些无用的没有设置过期时间的key。大概有500多w的key。虽然key的数目听起来挺吓人。但是自己玩redis也有年头了,这种事还不是手到擒来?
当时想了下,具体方案是通过lua脚本来过滤出500w的key。然后进行删除动作。lua脚本在redis server上执行,执行速度快,执行一批只需要和redis server建立一次连接。筛选出来key,然后一次删1w。然后通过shell脚本循环个500次就能删完所有的。以前通过lua脚本做过类似批量更新的操作,3w一次也是秒级的。基本不会造成redis的阻塞。这样算起来,10分钟就能搞定500w的key。
然后,我就开始直接写lua脚本。首先是筛选。
用过redis的人,肯定知道redis是单线程作业的,肯定不能用keys命令来筛选,因为keys命令会一次性进行全盘搜索,会造成redis的阻塞,从而会影响正常业务的命令执行。
500w数据量的key,只能增量迭代来进行。redis提供了scan命令,就是用于增量迭代的。这个命令可以每次返回少量的元素,所以这个命令十分适合用来处理大的数据集的迭代,可以用于生产环境。
scan命令会返回一个数组,第一项为游标的位置,第二项是key的列表。如果游标到达了末尾,第一项会返回0。
2
所以我写的第一版的lua脚本如下:
local c = 0
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)
end
end if 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)
end
end return c
在本地上传后执行:
可以看到,scan命令没法完全保证每次筛选的数量完全等同于给定的count,但是整个迭代却很好的延续下去了。最后也得到了游标返回0,也就是到了末尾。至此,测试数据20w被全部删完。
这段lua只要在套上shell进行循环就可以直接在生产上跑了。经过估算大概在12分钟左右能删除掉500w的数据。
知其然,知其所以然。虽然scan命令以前也曾玩过。但是的确不知道其中的细节。况且文档的翻译也不是那么的准确,以至于自己在面对错误的结果时整整浪费了近1个多小时的时间。记录下来,加深理解。
Redis 中 scan 命令太坑了,千万别乱用!!的更多相关文章
- Redis中的Scan命令踩坑记
1 原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作.但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限.所以记录下这 ...
- 用redis的scan命令代替keys命令,以及在spring-data-redis中遇到的问题
摘要 本文主要是介绍使用redis scan命令遇到的一些问题总结,scan命令本身没有什么问题,主要是spring-data-redis的问题. 需求 需要遍历redis中key,找到符合某些pat ...
- redis中keys命令带来的线上性能问题
起因 下午接到运维反馈,生产redis有个执行keys的命令请求太慢了,要两三秒才能响应 涉及命令如下: KEYS ttl_600::findHeadFootData-15349232-*-head ...
- redis中scan和keys的区别
scan和keys的区别 redis的keys命令,通来在用来删除相关的key时使用,但这个命令有一个弊端,在redis拥有数百万及以上的keys的时候,会执行的比较慢,更为致命的是,这个命令会阻塞r ...
- redis 《scan命令》
此命令十分奇特建议参考文档:http://redisdoc.com/database/scan.html#scan 222222222222222并非每次迭代都要使用相同的 COUNT 值. ...
- redis中set命令的源码分析
首先在源码中的redis.c文件中有一个结构体:redisCommand redisCommandTable[],这个结构体中定义了每个命令对应的函数,源码中的set命令对应的函数是setComman ...
- Redis中常用命令
连接操作相关的命令 quit:关闭连接(connection) auth:简单密码认证 持久化 save:将数据同步保存到磁盘 bgsave:将数据异步保存到磁盘 lastsave:返回上次成功将数据 ...
- Python Redis中Scan遇到问题
在项目启动中需要删除redis原先相同key储存的值,所以使用scan_iter来便利相关的key,并删除. 这里需要注意两个性能问题 1. scan_iter的模糊匹配的过滤器要正确,否则会带来很多 ...
- 8、Redis中sort命令详解
写在前面的话:读书破万卷,编码如有神 ------------------------------------------------- 1.排序 (1)sort:可以对List.Set.ZSet里面 ...
- redis scan 命令指南
redis scan 命令指南 1. 模糊查询键值 redis 中模糊查询key有 keys,scan等,一下是一些具体用法. -- 命令用法:keys [pattern] keys name* -- ...
随机推荐
- NC16527 [NOIP2013]货车运输
题目链接 题目 题目描述 A 国有 n 座城市,编号从 1 到 n ,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆 ...
- 图解3种常见的深度学习网络结构:FC、CNN、RNN
01 全连接网络结构 全连接(FC)网络结构是最基本的神经网络/深度神经网络层,全连接层的每一个节点都与上一层的所有节点相连. 全连接层在早期主要用于对提取的特征进行分类,然而由于全连接层所有的输出与 ...
- 使用virtualenv新建django项目
# 安装virtualenv pip install virtualenv # 创建虚拟环境 virtualenv venv # 进入虚拟环境 venv\Scripts\activate # 安装dj ...
- 苹果AppleMacOs最新Sonoma系统本地训练和推理GPT-SoVITS模型实践
GPT-SoVITS是少有的可以在MacOs系统下训练和推理的TTS项目,虽然在效率上没有办法和N卡设备相提并论,但终归是开发者在MacOs系统构建基于M系列芯片AI生态的第一步. 环境搭建 首先要确 ...
- C#-SendKeys方法的使用
模拟键盘CTRL+S 的输入 using System; using System.Collections.Generic; using System.Diagnostics; using Syste ...
- pikachu SQL-inject insert/update注入
insert 注入 (修改信息处是update注入,和此处同理) 注册页面,用户处输入 1' 发现报错信息 You have an error in your SQL syntax; check th ...
- AI与人类联手,智能排序人类决策:RLHF标注工具打造协同标注新纪元,重塑AI训练体验
AI与人类联手,智能排序人类决策:RLHF标注工具打造协同标注新纪元,重塑AI训练体验 在大模型训练的 RLHF 阶段,需要人工对模型生成的多份数据进行标注排序,然而目前缺乏开源可用的 RLHF 标注 ...
- 运行Study.Trade模块的Web.Unified.Host
1.把Web.Host的项目设置为启动项目 上一篇文章报错,因为npm功能没有安装,导致Web.Unified.Host的wwwroot下没有libs目录. 2.默认是在IIS Express中承载 ...
- 【Azure 应用服务】应用服务连接 Azure MySQL 一直失败,报错 Create connection error
问题描述 App Service上部署的Java应用,连接 Azure Database for MySQL 失败.错误信息:Create connection error, url: jdbc:my ...
- 【Azure 事件中心】Spring Boot 集成 Event Hub(azure-spring-cloud-stream-binder-eventhubs)指定Partition Key有异常消息
问题描述 在Spring Boot应用中集成Event Hub,发送消息时指定Partition Key,日志中发现异常: 应用使用Event Hub版本为 <dependency> &l ...