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

Redis顺序集合那些事儿,怎么用起来更顺手更高效

Redis的有序集合,你可以把它想象成一个班级的成绩单,这个成绩单有两个核心特点:第一,每个学生(成员)都有一个分数;第二,这个名单是按照分数从高到低或者从低到高排好序的,在Redis里,这个“分数”是一个浮点数,排序就是靠它。

(来源:Redis官方文档对Sorted Sets数据类型的核心定义)

这个东西在实际项目中用处非常大,最经典的应用就是排行榜,游戏里的玩家积分榜、微博的热搜榜、视频网站的热播榜,都可以用它来实现,你只需要把商品ID或者话题名称作为“成员”(member),把销量、点击量作为“分数”(score),然后一个命令就能取出前N名,非常方便。

(来源:常见应用场景的归纳)

除了排行榜,它还能做延时队列,比如你有一些任务需要在5分钟后执行,你可以把任务的执行时间戳作为分数,把任务内容作为成员,塞进有序集合里,然后启动一个进程,每隔一段时间就去这个集合里扫描一下,找出分数小于当前时间戳的任务(也就是已经到点的任务),拿出来执行,这比简单的列表队列更强大。

(来源:Redis在消息队列和延时任务中的实践应用)

怎么用才能更顺手和高效呢?这里有几个关键点。

第一,理解命令的复杂度,Redis命令很快,但不同的命令速度不一样,像ZADD(添加成员)、ZREM(删除成员)、ZSCORE(获取成员分数)这些命令,时间复杂度是O(1)或者O(log(N)),都非常快,你可以放心频繁使用,像ZRANGE(按排名范围取成员)和ZREVRANGE(逆序按排名范围取成员)这样的命令,如果你要取整个集合,复杂度是O(log(N)+M),其中N是集合总大小,M是你返回的成员数量,如果整个集合有几百万成员,你一次性全取出来,肯定会慢。关键是要避免一次性操作大量数据,尽量只取你需要的部分,比如只取前100名。

Redis顺序集合那些事儿,怎么用起来更顺手更高效

(来源:Redis官方文档中每个命令的时间复杂度说明)

第二,善用范围操作,有序集合最强大的地方就在于范围查询,除了按排名范围查(第1名到第10名),还可以按分数范围查(分数在80到100之间的成员),命令是ZRANGEBYSCOREZREVRANGEBYSCORE,这在做分页或者筛选时极其有用,你想找出所有积分在1000分以上的用户,用这个命令一下就搞定了,效率远高于你把所有数据取到程序里再过滤。

(来源:Redis范围查询命令的功能介绍)

第三,谨慎使用聚合操作,有序集合支持对多个集合进行并集(ZUNIONSTORE)和交集(ZINTERSTORE)操作,这个功能很强大,比如你可以计算两个频道的热门文章合集,但是要注意,这些命令的时间复杂度是O(NK)+O(Mlog(M)),其中N是最小集合的大小,K是输入集合的个数,M是结果集中元素的数量,当集合很大时,这个操作可能会阻塞Redis服务器一段时间,不适合在线上频繁执行,如果一定要用,可以考虑在低峰期执行,或者把结果缓存起来。

Redis顺序集合那些事儿,怎么用起来更顺手更高效

(来源:对聚合命令性能影响的实践经验总结)

第四,注意大容量的影响,当一个有序集合的元素数量非常庞大时(比如上千万),即使是一些O(log(N))的操作,也可能因为常数因子变大而感觉变慢,内存占用也会成为一个问题,Redis为了优化内存,在有序集合的元素较小时使用ziplist(一种紧凑的内存结构),当元素数量或大小超过阈值时,会转换为skiplist(跳跃表),你可以根据实际情况,在Redis配置文件中调整这个转换的阈值(zset-max-ziplist-entrieszset-max-ziplist-value),如果你的集合大部分都很大,那么用默认的skiplist就好;如果你的集合大部分都是小集合,适当调大阈值可以节省很多内存。

(来源:Redis内存优化相关的配置文档和原理分析)

一个实用的技巧是组合使用,有序集合经常和Redis的其他数据结构一起用,在做排行榜时,有序集合里只存用户ID和分数,用户的详细信息(如昵称、头像)存在Hash(哈希表)里,这样,你先用ZREVRANGE取出前10名的用户ID,再用HMGET批量获取这10个用户的详细信息,通过两次高效的网络IO就完成了,比把详细信息都塞进有序集合里要灵活和节省空间得多。

(来源:常见的Redis多数据结构组合使用模式)

想让Redis有序集合用得顺手高效,核心就是:了解每个命令的“代价”,多用高效的范围查询,避免阻塞性的聚合操作和大数据量传输,根据数据规模优化内存配置,并善于将它和其他数据结构搭配使用,把它当成一个功能强大的、现成的排序引擎来用,而不是一个万能仓库,这样就能最大程度发挥它的威力。