当前位置:首页 > 问答 > 正文

Redis里那些模糊删除的细节其实挺强大,很多人没注意到怎么用好它

主要整合自Redis官方文档关于KEYSSCAN命令的说明、以及多位资深开发者在技术社区如Stack Overflow、知乎专栏上分享的实践经验与案例分析。)

很多人用Redis,都知道可以用KEYS pattern这个命令来查找匹配特定模式的键,比如KEYS user:*就能找出所有以user:开头的键,很自然地,下一步就是想删除这些键,新手常见的做法是,先用KEYS命令查出所有匹配的键,然后再用DEL命令一个个或者批量删除,这个方法在开发测试环境、数据量很小的时候,看起来没问题,挺顺手,但一旦放到线上的生产环境,Redis里存了几个G、甚至几百个G的数据,键的数量达到百万、千万级别,这个操作就变成了一个可怕的“性能杀手”,搞不好能把整个Redis服务直接打挂。

为什么这么危险呢?根源在于KEYS命令的特性。KEYS命令在执行的时候,它会一次性遍历整个数据库里的所有键,然后把你匹配的那个模式的所有键都找出来,再返回给你,这个过程是阻塞式的,也就是说,在Redis正在吭哧吭哧地帮你遍历整个键空间的那段时间里,它没办法处理其他任何来自客户端的请求,如果你的数据库特别大,这个遍历过程可能会需要好几秒钟,甚至更长时间,在这几秒钟里,你的应用所有试图读写Redis的操作都会卡住、超时,导致服务不可用,这简直就是一场灾难,Redis官方自己也强烈建议,绝对不要在生产环境使用KEYS命令。

正确的、高级的模糊删除姿势是什么呢?答案是使用SCAN命令加上DEL命令。SCAN命令就是为解决KEYS命令的阻塞问题而生的,它不是一个一次性返回所有结果的命令,而是一个迭代器,你第一次执行SCAN 0 MATCH pattern,它会返回一小部分匹配的键(比如几十个或者几百个,可以自己设定数量),同时返回一个用于下一次迭代的游标(cursor),然后你用这个新的游标再次执行SCAN,它又会返回下一小批键,这样一点点地、分批次地遍历整个数据库,直到游标返回为0,表示遍历完成。

这个过程最大的好处就是非阻塞,因为每次SCAN命令只获取一小部分键,执行速度非常快,只会占用极短的瞬间,对Redis服务的性能影响微乎其微,不会导致服务卡顿,你可以写一个简单的脚本或者程序,用一个循环,不断地调用SCAN,每次拿到一小批键的列表后,紧接着就用DEL命令(支持一次删除多个键)把这批键删除掉,这样,整个模糊删除的过程就被“化整为零”了,从一次性的、长时间的重体力活,变成了多次的、短时间的轻量级操作,对线上服务非常友好。

Redis里那些模糊删除的细节其实挺强大,很多人没注意到怎么用好它

用好SCAN命令进行模糊删除,还有一些非常关键的细节需要注意,这些细节才是真正体现其强大和需要技巧的地方,也是很多人容易忽略的:

第一点,遍历过程中数据可能发生变化,你正在用SCAN遍历的时候,其他的客户端可能正在往数据库里新增符合你模式的键,或者删除一些键。SCAN命令本身不保证能返回所有在遍历开始时存在的键(如果期间有键被删除),它也可能返回在遍历开始之后新增加的键,对于删除操作来说,这个特性通常是可以接受的,因为我们的目标是清理掉符合模式的键,即便有新增加的,大不了下次清理任务再把它删掉,或者这次运气好也能扫到它,但你需要心里有数,知道这个过程不是绝对的“原子快照”。

第二点,游标的含义很特别SCAN命令的游标不是我们传统理解的那种递增数字索引,它对应的是Redis内部字典的槽位(slot),即使你在迭代过程中,数据库里的键发生了变化(有增有删),SCAN命令仍然能依靠这个游标机制,保证大体上遍历完所有的槽位,所以即使在遍历过程中有键被修改,它也能在很大程度上保证所有键都会被遍历到,不会出现无限循环或者严重遗漏的情况,这个内部实现细节保证了SCAN的健壮性。

Redis里那些模糊删除的细节其实挺强大,很多人没注意到怎么用好它

第三点,控制每次迭代的COUNT值SCAN命令允许你设置一个COUNT参数,来建议每次迭代返回多少元素,但要注意,这只是一个“建议值”,Redis不一定完全听话,有时候返回的可能会比COUNT多,也可能少,这个值需要根据实际情况调整,设置得太小,会导致总的迭代次数增多,网络往返次数变多;设置得太大,又可能单次SCAN操作耗时变长,虽然不会像KEYS那样完全阻塞,但占用时间过长也不好,通常需要根据数据量和服务器性能做一个平衡。

第四点,更精准的模式匹配和类型过滤SCAN命令不仅支持MATCH模式来匹配键名,还支持TYPE过滤器来指定键的数据类型,你不仅想删除所有以cache:开头的键,还想确保只删除那些数据类型是string(字符串)的键,避免误删了hashset等其他结构的数据,你可以使用SCAN cursor MATCH cache:* TYPE string这样的命令组合,这样就更精准、更安全了,这是KEYS命令完全不具备的能力。

第五点,对于超大数据集的耐心,使用SCAN进行模糊删除,相当于是在“后台”悄悄地、缓慢地进行清理,如果数据量极其庞大,这个过程可能需要几分钟甚至更长时间才能完全结束,你需要确保你的删除脚本能够稳定运行,并且要能够监控它的进度(通过游标的变化可以大致估算),不要指望它像KEYS+DEL那样(虽然危险)能“秒完”。

Redis提供的SCAN家族命令(除了通用的SCAN,还有针对特定数据类型的SSCAN, HSCAN, ZSCAN),为我们在生产环境中安全、高效地管理大量数据(尤其是模糊删除)提供了强大的工具,它的强大之处不在于速度有多快,而在于它的“非阻塞”和“可迭代”的特性,使得大规模数据操作变得可控、平滑,真正用好它,关键就在于理解并处理好上述那些细节:采用迭代分批的方式、理解游标和遍历的语义、合理设置参数、利用好类型过滤,并且对整个过程有正确的预期和监控,避开KEYS的坑,拥抱SCAN的细节,这才是高效、负责任地使用Redis的进阶之道。