滚动排行榜与一般排行榜的最大区别在于数据的存储和查询方式。在一般排行榜中,起始点是固定的,例如某一周或某一个月,存储和查询时的时间范围也是固定的。而滚动排行榜则不同,在滚动排行榜中,榜单的取值范围是随着时间的变化而不断变化的。
对于常用的Redis实现方案来说,如果是固定排行榜,我们只需要在每个固定的时间点将数据存入相应的Key中,查询时只需使用一条命令即可获取。因为在同一时间段内的任何一天都属于同一个周期。
然而,对于滚动排行榜,比如近7天榜,每次查询时都需要对今天及前6天的数据进行聚合计算才能得到最新的排行榜结果。在这种情况下,仅仅使用固定的Key无法解决问题,因为数据的存储和查询方式都需要特殊处理,以确保数据能够快速可用。
具体来说,在实现滚动排行榜时,我们可以采取以下策略:
1.存储方式:使用有序集合 (Sorted Set)存储每个时间点的排行榜数据,按照时间顺序存储,最新的数据排在前面。
2.数据更新:每次有新的数据需要加入排行榜时,先将其存入相应的时间点的有序集合中,然后根据设定的时间范围删除过期的数据,以保持榜单数据的更新和滚动。
3.查询方式:为了获得近7天(或其他时间范围)的排行榜数据,需要将对应时间范围内所有的Sorted Set进行聚合操作,计算出最终的排名结果。
通过这种存储和查询方式,我们能够实现滚动排行榜的功能。但是需要注意,在查询时由于需要对多个有序有序进行聚合计算,可能会对性能造成一定的影响,因此在实际实现中需要进行优化,例如增量计算、缓存等手段来提高查询效率。
实现有序集合的第一种方式:
- 同时写n天的滚动榜单
实现一个同步写n天滚动榜单的方法,可简单地理解为与固定榜单的实现方式相同。以Redis实现7天滚动方式为例,假设我们每天使用礼物排行榜的键值为"gift_list_cache:20230101",类型选择有序集合 (ZSet)。每当用户获得一个礼物,其对应的价值就会增加,最终用于统计排行榜上主播的总收礼情况。
以下是用户ID为Test0001的用户在获得价值为13140的礼物时,以及获取排行榜数据的示例命令:
java
// 增加排行榜用户数据
redis.zincrby("gift_list_cache:20231225", 13140, "Test0001");
1.即每天一个键名,将当天的数据写入当天的键中
2.不仅要写入当天的键,还要针对接下来的6天进行写入(ZINCRBY),每次写入操作都按照这个逻辑执行。
类似我们从2023年1月1日开始做滚动榜单,那么在1号这一天我们要插入的榜单就是:
java
// 增加1号榜单数据
redis.zincrby("gift_list_cache:20230101", 13140, "Test0001");
// 增加2号榜单数据
redis.zincrby("gift_list_cache:20230102", 13140, "Test0001");
// 增加3号榜单数据
redis.zincrby("gift_list_cache:20230103", 13140, "Test0001");
// 增加4号榜单数据
redis.zincrby("gift_list_cache:20230104", 13140, "Test0001");
// 增加5号榜单数据
redis.zincrby("gift_list_cache:20230105", 13140, "Test0001");
// 增加6号榜单数据
redis.zincrby("gift_list_cache:20230106", 13140, "Test0001");
// 增加7号榜单数据
redis.zincrby("gift_list_cache:20230107", 13140, "Test0001");
2号需要插入的榜单数据就是:
java
// 增加2号榜单数据
redis.zincrby("gift_list_cache:20230102", 13140, "Test0001");
// 增加3号榜单数据
redis.zincrby("gift_list_cache:20230103", 13140, "Test0001");
// 增加4号榜单数据
redis.zincrby("gift_list_cache:20230104", 13140, "Test0001");
// 增加5号榜单数据
redis.zincrby("gift_list_cache:20230105", 13140, "Test0001");
// 增加6号榜单数据
redis.zincrby("gift_list_cache:20230106", 13140, "Test0001");
// 增加7号榜单数据
redis.zincrby("gift_list_cache:20230107", 13140, "Test0001");
// 增加8号榜单数据
redis.zincrby("gift_list_cache:20230108", 13140, "Test0001");
以此类推,那么后面每天都是要插入当天和后面6天的榜单数据,那么这样操作到7号的时候,7号的当天榜单数据就已经是前7天的滚动榜单了。
具体代码实现:
java
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230101", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230102", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230103", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230104", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230105", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230106", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
rankScoreUtilManager.addScoreToRank("gift_list_cache:20230107", anchorId, BigDecimal.valueOf(value), EeDateUtil.toDate(eventTime));
我这里的代码只是为了方便,给大家解释逻辑,(具体榜单的大部分源码可以查看之前的文章:Redis实现日榜|直播间榜单|排行榜|Redis实现日榜01)但是真实开发的时候最好不要这样写,因为这样写并不能保证7个榜单同时插入的原子性,最好把6个榜单的key都写在lua脚本中同步执行。
优点:
- 实现简单,获取数据也简单,直接获取即可使用。
缺点:
- 每次写入需要写入多个键名。不将部分写入失败作为缺点是因为其他方案在不严谨的情况下也可能存在此问题。如果只是实现7天榜单,且不想浪费过多精力,可以采用此方案。但是如果天数较多,比如30天滚动榜单,则显然不适合。后续还会介绍另外两种滚动榜单的实现方式。