今天和同事讨论一个问题:如何从redis的多个zset中汇总并过滤数据给客户端。
你听起来可能有点费劲儿,我再来稍微描述一下场景:
假设现在我们的redis中有2个zset,分别如下:
one => a,80
b,75
c,60
two => x,99
y,66
z,100
不熟悉redis或者zset用法的童鞋,请自己脑补。
我们假设 one
和 two
这两个集合中存放的是两个班级学生的名字和总分(不考虑学生重名的问题)。那么,现在我们要在系统中显示一个列表,该列表要按照分数排列显示这两个班级的学生名单,考虑到例子里的数据并不多,那我们只能假设每一页只显示3个人。
场景描述到这里,应该就足够清晰了~~
解决方案很多,我们以最佳性能为基准,来评论方案的好坏。当然,最简单的是从分别从2个集合中取出所有数据,然后在应用系统中进行排序和截断。但不用多想,这种方法是最吃力不讨好的,为什么这么说?首先要从redis中拿出全部数据,这就已经不太合理了,还要自己实现一个排序算法~~我不往下说了!
还可以使用zset提供的ZUNIONSTORE,调用后它会在redis中创建一个新的zset集合,不过却可以帮我们提供排序和截断方法,从另一个角度想,新建的这个zset集合其实充当了“缓存”的作用,只要在应用逻辑中先检查是否存在这个key,就避免了每次都做合并操作。但,数据更新后,如何即时的更新这个“缓存”就成了问题,这涉及到定时任务的话题,就不展开了,总之知道这种方法的利弊即可。当然你也可以说每次都做合并操作,但考虑到这个合并操作的时间复杂度是 O(N)+O(M log(M)),再加上并发请求,我可以认为,这是在玩火,但至于redis是内部如何处理并发创建集合的,是否有多线程保护等,我就不得而知了。总之,不推荐这么做。
步入正题,现在我们延伸一下场景,如果我们给出一个学生名单{a,c,x,z},我要求你在上述方案2中拿到的汇总集合上做一次子集的排序获取,并按照前端页面的要求,根据分数顺序只取前三个用于显示。
这个名单中的学生,由于分数是错乱的,所以你该怎么办?分别获取每一个学生在集合中的score,再在应用系统中做排序和截断?这又回到了之前的那个方案1……
好吧,我们还可以构建一个zset,如下:
list => a,0
c,0
x,0
z,0
然后执行:
//total代表学生总集合
//target表示我们临时创建的目标集合
zinterstore target 2 list total
这样就拿到了我们学生名单中的所有学生的zset集合,然后可以根据显示需求做分页显示了。这么做的问题我在方案2中已经提过了,就不多说了。总之觉得这么做很不尽人意!
其实说到这里,也就没什么好办法了,不过翻了翻文档,发现redis2.8以后,提供了一个新的操作:ZSCAN。
我擦,以为找到了希望,眼睛瞬间冒了绿光。尝试这么做:
zscan total a|c|x|z 3
我觉得这么做太爽了,要是真的管用,那就更好了T_T
返回的结果里,毛都没有,哇哈哈哈~为什么呢?难道是我的正则写错了?换了好几种写法,还是不行。
最后发现,原来,redis命令中所谓的pattern模式参数,并不支持正则这么强大的规则,我们可以在这里看到redis的pattern所支持的模式。
额,貌似有点悲剧的味道~~~