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

用 Redis 怎么搞模糊键匹配,实践里那些坑和技巧分享

用Redis做模糊键匹配,最直接想到的命令就是KEYS,这个命令用起来很简单,比如你想找所有以"user:session:"开头的键,直接敲KEYS user:session:*就行了,它会把你库里所有符合这个模式的键都列出来,在实践里,几乎所有的老司机都会告诉你:不要在线上生产环境用KEYS命令。

为啥呢?这得从Redis的老底说起,Redis是单线程的,它同一时间只能干一件事。KEYS命令会一次性遍历整个数据库的所有键,如果你的数据库里有几百万、几千万个键,这个命令就会像一列长长的火车一样,阻塞住Redis服务器,在这趟火车完全跑完之前,Redis啥也干不了,不能响应其他的读写下单、查询请求,后果可能就是服务卡顿甚至超时崩溃,这可以说是KEYS命令最大的一个“坑”。(来源:Redis官方文档对KEYS命令的警告)

那不用KEYS,我们用什么?答案是SCAN命令。SCANKEYS的“安全升级版”,它也是用来做模式匹配的,但它的工作方式完全不同:它不是一次性扫完全库,而是分批分期地扫描,你第一次执行SCAN,它给你返回一小部分匹配的键和一个游标(cursor),你拿着这个游标下次再执行SCAN,它再从上次停下的地方继续扫描下一批,这样就像蚂蚁搬家,一点一点来,每次只占用一点点服务器资源,不会造成长时间的阻塞。

SCAN用起来也比KEYS麻烦点,需要你写个循环,一次次调用,直到游标返回0,表示扫描完成了。SCAN还有一个特点需要注意:它不保证每次迭代返回的数量,甚至可能会返回重复的键,这是因为它是基于当时数据库的“字典”状态进行遍历的,如果在扫描过程中,有键被增加或删除,就可能出现某个键被多次遍历到或者漏掉的情况。SCAN适用于那种你只是大概想找出一些键进行处理,对“完全精确、不重不漏”要求不高的场景,如果你需要强一致性,就得想别的办法了,比如在扫描期间避免数据变更。(来源:Redis官方文档对SCAN命令的说明)

除了选择命令,键的设计本身就是避免模糊匹配的关键技巧,很多时候,你发现自己需要频繁地做模糊查询,可能是一开始键的结构没设计好,一个好的实践是使用有层次、有规律的键名,存储用户信息,别用user123, user456这种,而是用user:123:profile, user:123:orders,或者更进一步,用user:123作为一个哈希表的键,里面存profile、orders等字段,这样,当你真的需要按模式找键时,模式可以更精确,比如user:123:*,能大大减少扫描的范围。

还有一个进阶技巧是利用数据结构和额外索引,举个常见的例子,你要管理用户的登录会话,所有会话键是session:随机ID,现在你想找出某个用户ID(比如10001)的所有活跃会话,直接用SCAN匹配session:*然后检查内容效率很低,这时候,你可以额外维护一个集合(Set)或有序集合(ZSet),键名叫user:10001:sessions,里面存的就是这个用户所有的会话ID,当你需要找这个用户的所有会话时,直接SMEMBERS user:10001:sessions或者ZRANGE user:10001:sessions 0 -1,速度是O(1)或O(log N),又快又准,完全避免了模糊匹配,这是一种“空间换时间”和“预先组织数据”的思路。

在使用SCAN进行模糊匹配时,模式(pattern)的写法也有讲究,Redis的模式支持简单的通配符,比如匹配任意多个字符,匹配一个字符,[abc]匹配括号内的某一个字符,尽量让模式更具体,比如user:session:auth:*就比user:*要好,能更快地排除不相关的键,提高扫描效率。

提醒一个容易被忽略的“坑”:Redis的数据库选择,如果你在使用SELECT命令切换数据库,SCAN命令的游标是特定于当前选中的数据库的,你不能在数据库0里拿到一个游标,然后切换到数据库1再去用这个游标继续SCAN,这样会报错,游标是和当前数据库状态绑定的。

Redis模糊键匹配的核心就是:弃用KEYS,拥抱SCAN;优化键设计,从源头上减少模糊查询的需求;必要时,巧用其他数据结构建立辅助索引,把这些技巧用好了,就能在享受Redis高速读写的同时,稳妥地处理需要模式匹配的场景。

用 Redis 怎么搞模糊键匹配,实践里那些坑和技巧分享