redis系列——keys真的屡试不爽吗?
keys
用过redis的宝子都知道,要从redis中根据条件查询特定前缀的key列表,可以这么做:
keys xxx*
简单暴力,爱咋咋地~
但是如果redis包含了200个w的key时,还能爱咋咋地吗?
首先,200w没有offset和limit,刷屏估计得刷好一会儿。。。
然后,keys算法是遍历算法,复杂度为O(n),如果是百万级以上的数量时,对于单线程的redis来说,一旦开始keys,其它请求都只能等(表面笑嘻嘻,心里MMP)。。。
所以,keys不是你想用就能用的。它的前置条件是key不多时。
scan
既然一屏到底的keys有严格的使用场景,那么有没替代的指令呢?有的,scan应运而生~
特点
相对keys,scan有以下几个特点:
- 和keys一样,它也提供模式匹配功能。
- 复杂度虽然也是O(n),但是增加了指定游标参数cursor和数目参数count,相当于增加了SQL查询的offset和limit。这样“分页查询“就实现了。这里需要注意的是,count并不是返回的条数,而是扫描的桶数。如下图的0、1、2、3...就是桶的下标。
- 返回的结果可能有重复,需要客户端进行去重处理。这里需要标红加粗。 scan在扫描数据时,相当于进行了分页处理,在分页读取的间隙,别的线程可能对扫描的键进行增删处理,这些处理会导致scan的cursor出现一定的偏差,导致扫描结果出现重复的键值对。应用程序需要特别处理这种情况。
- 遍历的过程中如果有数据修改,修改后的数据能否遍历到是不确定的。 原因和上一点类似。
- 单次返回结果为空并不意味着遍历结束,需要根据返回的cursor是否为0来确定是否遍历结束。
参考代码
package main
import (
"github.com/go-redis/redis"
"log"
)
func main() {
// init redis manager
mgr := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
})
if mgr == nil {
log.Fatalf("redis.NewClient failed.")
return
}
hkey := "hahatables"
//// 插入hkey
//keyNum := 100
//for i := 0; i < keyNum; i++ {
// cmd := mgr.HSet(hkey, strconv.Itoa(i), "value"+strconv.Itoa(i))
// if cmd.Err() != nil {
// log.Fatalf("redis.HSet(%s) failed : %v", hkey, cmd.Err())
// return
// }
//}
var (
total int
keyResult []string
subKey = "7*"
count = int64(1000)
cursor uint64
keys []string
err error
)
cmd := mgr.HScan(hkey, cursor, subKey, count)
for {
if keys, cursor, err = cmd.Result(); err != nil {
log.Fatalf("hscan cursor %v failed : %v", cursor, err)
return
}
total += len(keys)
keyResult = append(keyResult, keys...)
if cursor > 0 { // 还有结果
cmd = mgr.HScan(hkey, cursor, subKey, count)
} else {
break
}
}
log.Printf("total : %v %v 7*s\n", total, keyResult)
}
这个例子中简单地测试了下hscan的使用,sscan、zscan功能类似。
总结
对于不确定key数量的查询,最好使用scan替换,避免出现因为keys指令导致redis卡顿的问题。
转载自:https://juejin.cn/post/7372734627163324426