redis 有序集合小试
2017-08-30 15:06
295 查看
假定一个使用场景:
排行榜。
接口1:返回TOP 10的选手信息及投票数
接口2:返回活动总参与选手数及总投票数
接口3:对于每个选手,返回自己的投票数,排名,距离上一名差的票数
如果直接对这张表做TOP 10的查询,则需要根据选手id做聚合查询,这样每次查询必然耗时。为了优化查询,可以增加另一张排行榜表,用一个定时任务每隔一段时间对原表做聚合查询,然后将结果写进排行榜表里,表里包含投票数及排名的字段,这样查询TOP 10和排名的时候直接查这张表。引入另一张表加快了性能,但牺牲了实时性,活动说明里需加上类似“榜单数据每10分钟同步一次”的话来告知用户。
投票操作如下:
命令格式:
zincrby key increment member
127.0.0.1:6379> zincrby vote 1 xiaodu
"1"
127.0.0.1:6379> zincrby vote 1 wangke
"1"
127.0.0.1:6379> zincrby vote 1 wanghaolong
"1"
然后集合中每个成员的值都是1
现在来看某一个成员的分数及排名
分数命令:
命令格式:zscore key member
127.0.0.1:6379> zscore vote xiaodu
"1"
查看他的排名
命令格式:127.0.0.1:6379> zrevrank vote xiaodu
(integer) 0
可以查看出在这个vote的集合中xiaodu排名为0,因为有序集合排序从0开始,0即为排名第一。
然后给wanghaolong这个成员分数加3
zincrby vote 3 wanghaolong
再来看小杜的排名
zrevrank vote xiaodu
“1”
然后看王浩龙的排名:
127.0.0.1:6379> zrevrank vote wanghaolong
(integer) 0
由此就能看出有序集合以分数高低来进行从高到低的排序。
获取前十名的排序
127.0.0.1:6379> zrevrange vote 0 9 从高到低
1) "wanghaolong"
2) "xiaodu"
3) "wangke"
4) "b"
5) "a"
其中成员a与成员b的分数都是1,b插入在a之前,由此得出如果分数相同那么插入顺序决定排序。
获取前十名以及她们的分数:从高到低
127.0.0.1:6379> zrevrange vote 0 9 withscores
1) "wanghaolong"
2) "4"
3) "xiaodu"
4) "2"
5) "wangke"
6) "1"
7) "b"
8) "1"
9) "a"
10) "1"
获取参与的成员数:
127.0.0.1:6379> zcard vote
(integer) 5
127.0.0.1:6379>
回到业务需求上:
大部分需求都已经得到满足,还剩下两个数据需要单独说一下。接口2中的总投票数没有直接的接口获得,一种方法是先用ZRANGE遍历所有的key,然后对score进行求和,另一种方法是对总票数单独用一个数据结构存储。接口3的距离上一名差的票数,先用ZREVRANK获取自己排名,然后用ZREVRANGE获取上一排名的分数,最后用自己的分数减去上一名的分数即可,代码示例python代码如下:php逻辑一样
另外如果两个key的score相同,排序逻辑是按照插入顺序排序。在有些情况下这个可能不满足实际要求,因此需要按实际情况重新设计key。比如如果要求同分数情况下按时间排序,那么key最好加上时间戳前缀。
排行榜。
场景
选手报名参加活动,观众可以对选手进行投票,每个观众对同一名选手只能投一票,活动期间最多投四票。后台需要提供如下接口:接口1:返回TOP 10的选手信息及投票数
接口2:返回活动总参与选手数及总投票数
接口3:对于每个选手,返回自己的投票数,排名,距离上一名差的票数
基于数据库的方案
首先需要一张表存储投票记录,一次投票就是一条记录。这张表相当于投票明细,判断每人只投一张票以及最多投四票都依赖对这张表的查询。如果直接对这张表做TOP 10的查询,则需要根据选手id做聚合查询,这样每次查询必然耗时。为了优化查询,可以增加另一张排行榜表,用一个定时任务每隔一段时间对原表做聚合查询,然后将结果写进排行榜表里,表里包含投票数及排名的字段,这样查询TOP 10和排名的时候直接查这张表。引入另一张表加快了性能,但牺牲了实时性,活动说明里需加上类似“榜单数据每10分钟同步一次”的话来告知用户。
基于redis的方案
对于排行榜的需求,redis有一个数据结构非常适合做这件事,那就是有序集合(sorted set)。redis的有序集合相关命令
有序集合和集合一样可以存储字符串,另外有序集合的成员可以关联一个分数(score),这个分数用于集合排序。下面以投票为例说明常见的命令,vote是有序集合的key。投票操作如下:
命令格式:
zincrby key increment member
127.0.0.1:6379> zincrby vote 1 xiaodu
"1"
127.0.0.1:6379> zincrby vote 1 wangke
"1"
127.0.0.1:6379> zincrby vote 1 wanghaolong
"1"
然后集合中每个成员的值都是1
现在来看某一个成员的分数及排名
分数命令:
命令格式:zscore key member
127.0.0.1:6379> zscore vote xiaodu
"1"
查看他的排名
命令格式:127.0.0.1:6379> zrevrank vote xiaodu
(integer) 0
可以查看出在这个vote的集合中xiaodu排名为0,因为有序集合排序从0开始,0即为排名第一。
然后给wanghaolong这个成员分数加3
zincrby vote 3 wanghaolong
再来看小杜的排名
zrevrank vote xiaodu
“1”
然后看王浩龙的排名:
127.0.0.1:6379> zrevrank vote wanghaolong
(integer) 0
由此就能看出有序集合以分数高低来进行从高到低的排序。
获取前十名的排序
127.0.0.1:6379> zrevrange vote 0 9 从高到低
1) "wanghaolong"
2) "xiaodu"
3) "wangke"
4) "b"
5) "a"
其中成员a与成员b的分数都是1,b插入在a之前,由此得出如果分数相同那么插入顺序决定排序。
获取前十名以及她们的分数:从高到低
127.0.0.1:6379> zrevrange vote 0 9 withscores
1) "wanghaolong"
2) "4"
3) "xiaodu"
4) "2"
5) "wangke"
6) "1"
7) "b"
8) "1"
9) "a"
10) "1"
获取参与的成员数:
127.0.0.1:6379> zcard vote
(integer) 5
127.0.0.1:6379>
回到业务需求上:
大部分需求都已经得到满足,还剩下两个数据需要单独说一下。接口2中的总投票数没有直接的接口获得,一种方法是先用ZRANGE遍历所有的key,然后对score进行求和,另一种方法是对总票数单独用一个数据结构存储。接口3的距离上一名差的票数,先用ZREVRANK获取自己排名,然后用ZREVRANGE获取上一排名的分数,最后用自己的分数减去上一名的分数即可,代码示例python代码如下:php逻辑一样
def get_next_step(redis_key, member): next_step = None score = redis.zscore(redis_key, member) rank = redis.zrevrank(redis_key, member) if rank > 0: next_member = redis.zrevrange(redis_key, rank - 1, rank - 1, withscores=True) next_step = next_member[0][1] - score return next_step
另外如果两个key的score相同,排序逻辑是按照插入顺序排序。在有些情况下这个可能不满足实际要求,因此需要按实际情况重新设计key。比如如果要求同分数情况下按时间排序,那么key最好加上时间戳前缀。
redis与数据库的同步
redis通常是作为缓存层加速查询的,如果数据没有做持久化则有概率会丢失数据。一个方案是用定时任务定时同步redis与数据库的数据,数据库里存储着原始数据,通过计算数据库的数据和redis做对比,可以修正由于redis不稳定导致的数据不一致。这里需要注意的是在同步过程时redis的数据有可能还在增长,因此最好先读redis的数据,然后记下时间,查询指定时间段里的数据库的数据,最后再用ZINCRBY增量修正redis数据,而不是直接用ZADD覆盖redis数据。总结
redis的有序集合是一个非常高效的数据结构,可以替代数据库里一些很难实现的操作。它的一个典型应用场景就是排行榜,通过ZRANK可以快速得到用户的排名,通过ZRANGE可以快速得到TOP N的用户列表,它们的复杂度都是O(log(N)),用来替代数据库查询可以大大提升性能。相关文章推荐
- Redis 有序集合
- redis 有序集合
- Redis有序集合命令ZRANGEBYSCORE|ZREVRANGEBYSCORE|ZREMRANGEBYRANK|ZREMRANGEBYSCORE
- python对redis的常用操作 下 (无序集合,有序集合)
- Redis-有序集合类型
- [Redis] 有序集合的操作
- Redis命令 -- 有序集合
- redis中Set集合SortSet有序集合的复制
- redisTemplate的有序集合(zset)实例
- 判断Redis有序集合中是否存在某个成员的方法
- 使用Redis有序集合搭建自有IP定位解析库(纯真库)
- Redis教程(六) 有序集合(soted set / zset)类型
- Redis系列学习笔记6 有序集合
- Redis有序集合
- Redis 小白指南(二)- 聊聊五大类型:字符串、散列、列表、集合和有序集合
- Redis(3-5):有序集合类型
- Redis数据结构和内部编码--有序集合(SortedSet)
- redis 有序集合数据结构实现 skiplist
- Redis有序集合命令ZREVRANGEBYLEX详解与应用
- Redis系列~有序集合(sorted set)(十)