排行榜是业务开发中常见的一个功能,如何设计一个好的数据结构能够满足实时的查询,下面我们一个实际的案例来探讨一下。
1. 场景
现在有一个积分捐赠活动,用户可以捐赠自己的积分。后台需要提供如下接口:
l接口1:返回捐赠积分TOP 15的用户信息以及捐赠的积分
l接口2:返回活动总参与人数
l接口3:对于每个用户,返回自己捐赠的积分与排名
2. 设计
2.1 数据库部分
如果每次查询TOP 10都去流水表聚拢数据的话,必须是非常耗时的。所以除了常规的表(例如用户资产表,用户资产流水表)之外,我们还设计一张用户资产开销的表,用于根据用户ID与活动ID进行数据聚拢。用户资产开销表中,用户ID与活动ID可以建立唯一索引,保证每个活动中,每个用户只有一条数据。这张表可以实时更新数据,也可以用定时任务去异步更新数据。
2.2 redis部分
虽然我们上面加了用户资产开销表,但是在数据量的越来越大,我们需要实现排行榜需求是不可能实时查表的。此时redis的有序集合(sorted set)就非常适合做这件事情。
2.3 redis有序集合的相关命令
有序集合和集合一样可以存储字符串,另外有序集合的成员可以关联一个分数(score),这个分数用于集合排序。
2.3.1 单权重排序
下面我们以积分捐赠为例讲一下涉及到的相关redis命令,其中donate_activity是有序集合的key。
2.3.2 复合权重排序
上面的例子中,只是列举了针对用户已捐的单权重场景,实际项目中,还会涉及多权重问题,例如根据用户已捐积分进行排名,但是同分数下先到达该分数的用户需要排在前面。因为redis有序集合的score是double类型,我们可以对已捐积分和用户开销表中该用户记录的最后修改时间做一个复合的权重。用户已捐积分作为整数位。以活动结束时间为界限,计算这个修改时间距离活动时间的时间戳作为小数位(这个时间需要做位数不足补0操作)。这样就能实现复合权重的排序功能了。
3. redis数据安全问题
在用户积分捐赠成功后,我们可以同步使用zincrby命令对该用户的排名权重进行叠加。但是我们不能完全相信redis里面的数据,因为redis通常是作为缓存层加速查询的,有概率丢失数据。为了解决这个问题,我们可以用定时任务定时从根据活动ID与用户ID聚拢数据的用户资产开销表中把数据更新到redis中。在同步过程中,如果使用zincrby来修正数据,可能涉及的中间计算会较多较繁琐,建议可以直接使用zadd覆盖redis数据(因为用户资产开销表的数据是最真实的)。
4. 总结
redis的有序集合在排序方面是一个非常高效的数据结构,可以替代数据库里一些很难实现的操作。它的一个典型场景就是排行榜,通过zrevrank可以快速得到用户排名,通过zrevrange可以快速得到top n的用户列表,它们的时间复杂度都是O(log(N))。
领取专属 10元无门槛券
私享最新 技术干货