使用Redis构建高效稳定低延迟的排行榜业务

news/2025/2/13 15:21:26/

一、业务描述
      现有一排行榜业务,数据库中拥有百万级用户,中秋佳节将要来临,用户可以写一篇关于中秋的故事,故事可邀请好友点赞,也可以打赏该故事,现在要挑选出关于中秋话题相关的故事,根据用户故事的点赞数、获得的打赏数以及故事查看次数为依据,根据一定规则算出后在客户端展示前100的故事排行榜。

二、业务分析
      分析业务得知,用户故事排行榜可不必实时刷新,比如5分钟或者是10分钟刷新一次,用户是没有多少感知的,如果涉及数据量太过庞大或者是业务计算复杂需要更多时间排行,完全可以明确的告知用户5分钟或者10分钟刷新一次排行榜,甚至是更久。面对这样的业务,不可能在用户请求的时候去计算排行,这样做服务器不仅响应慢,服务器还会消耗大量的资源,比如大量的逻辑计算导致服务器CPU爆满,内存消耗过大等问题。要想用户的请求能够很快的响应,在查询时这个排行榜就已经排好了。下面我介绍一种我在工作中所使用的一种方法,也许并不是最好的,但满足一般的业务还是没有任何问题的,对于超大的数据集,需要另觅方法。

三、方法介绍
      合理使用Redis的有序集合。Redis是一种将数据放到内存中的数据库,面对排行榜这样的业务需求,找它是完全没有问题的。更值得拍手叫好的是,Redis的有序集合可以帮助我们实现排行榜,将多个维度的数据源取得之后,通过一定的规则计算出分值,把这些数据放入到Redis的有序集合中,那么排行结果自然而然的就出来了,取出排行数据后,在填充一些客户端展示需要的数据即可。要想实现每隔一分钟或是5分钟刷新一次,只需开启一个定时器,定时去调用排行计算逻辑。更多关于Redis的有序集合的命令,请参考链接 http://doc.redisfans.com/ 或是Redis官网 https://redis.io/ 。

四、具体实现
      实例以上诉故事业务为题,列出部分代码,仅供参考。
(1) 获取故事并计算排行分值。从多个数据库中取得关于中秋相关话题的故事,得到故事后在根据故事的id取得故事的一些统计信息,比如故事点赞数、获得的打赏数、查看数等。(注意:在将分值放到Redis时,一定要将故事Id与分值一一对应,否则在取数据时并不知道分值对应的故事是谁)

/*** 计算排行榜分值*/
public void calStoryRankList() {// 取得故事List<Story> storyList = storyService.getFestivalStoryList();if (CollectionUtils.isEmpty(storyList)) {return;}// 得到故事的idList<Long> storyIds = levyArticleStoryList.stream().map(story -> story.getId()).collect(Collectors.toList());// 根据故事id批量取得故事的统计信息 Map<Long, StoryStat> storyStats = statService.getStoryStats(storyIds);// 保存根据排行规则得到的分值Map<Long, Double> storiesRankMap = new HashMap<>(levyArticleStoryList.size());// 计算排行分值storyList.stream().forEach(story -> {StoryStat storyStat = stats.get(story.getId());if (null != storyStat) {Long priseCount = storyStat.getPriseCount();Long beansCount = bbStoryStat.getTotalEarning();Double score = priseCount + beansCount / 100.0;// 保留一位小数score = Math.round(score * 10) / 10.0;storiesRankMap.put(story.getId(), score);}});// 将分值放到Redis中rankListDao4Redis.putStoryRankList(storiesRankMap);
}

(2) 将计算好的排行分值放到Redis中。这里需要考虑几个问题,如果排行榜在第一次刷新时A故事是存在的,但在下次刷新时A故事被作者或者是运营人员删除了,那么Redis中应该去除这条废弃的数据,因此,这里我们需要使用Redis的删除键命令将这个键下的所有数据删除,在存入新的数据,如果新的数据存入失败,那么之前的删除操作应该回滚,此时可以使用Redis的事务控制。如果不这样做,会导致排行榜出现空的情况。详情见代码。

  /*** 设置征文排行数据** @param stories* @return*/public Boolean putStoryRankList(Map<Long, Double> stories) {// 根据规则生成Redis的key键String rankKey = RedisKeys.keyOfStoryRankList();// 自行在Service层注入jedisPipeline pipelined = jedis.pipelined();// 开启事务pipelined.multi();// 删除以前的数据pipelined.del(rankKey);// 存入新的值pipelined.zadd(rankKey , stories);pipelined.exec();pipelined.sync();return true;    
}

(3) 取得排行榜。有了前面的铺垫,取得排行榜就变得非常简单了。此处没有给全分页的实现,有兴趣的自己尝试做一下。实现如下:

public Map<Long,Double> getStoryRankList(int size, int offset) {// 根据规则生成Redis的key键String rankKey = RedisKeys.keyOfStoryRankList();Pipeline pipelined = jedis.pipelined();// 取得排行榜,可以加入分页,需要注意Redis的取数据时根据数组下表来取的,因此,如果按照MySQL数据库的规则,那么size需要减去1Response<Set<Tuple>> scores = pipelined.zrevrangeWithScores(levyRankKey, offset, size - 1);// 保存排行结果的MapMap<Long,Double> result = new HashMap<>();for (Tuple score : scores.get()) {result.put(Long.parseLong(score.getElement().toString()), score.getScore()));               }return result;
}

(4) 填充数据。因为从Redis得到的排行榜数据仅仅包含故事的Id,很明显,这样的数据用户是无法识别的,因此,在此步仅仅是根据故事的ID去填充排行展示所需要的数据,具体业务,就得具体分析,此处不在给出代码。

五、总结
      从这个例子看出,Redis给我们带了很大遍历,减少了实现业务的难度和工作量。倘若你所在的公司没有使用Redis(这样的情况应该不存在),那么要想实现一个高效且稳定的排行榜业务,还是得有两把刷子,比如自己使用缓存实现。Redis给我提供的便利不仅仅是这些,还有很多有用的东西等待我们去挖掘,本篇博文仅仅是给没有蓝图的同僚一个小小的启发,由于小编能力有限,文中难免有纰漏之处,还望指出,谢谢!


http://www.ppmy.cn/news/191588.html

相关文章

PyFlink 有状态流处理实例 实时排行榜

01 UDAF 聚合函数的使用 自定义聚合函数&#xff08;UDAF&#xff09;&#xff0c;将多条记录聚合成一条记录。其输入与输出是多对一的关系&#xff0c;即将多条输入记录聚合成一条输出值。 需要注意的是&#xff1a;当前聚合函数仅在流模式下的 GroupBy 聚合和 Group Window…

排行榜奖励发放

一般来说排行榜奖励都通过邮件来发放&#xff0c;要不就是对于在线用户直接发给用户自身&#xff0c;对于离线用户发到用户的离线邮箱&#xff0c;用户上线可以通过邮件取到。这样做简单直接&#xff0c;易于处理&#xff0c;也不容易出错&#xff0c;但是在用户很多的时候可能…

字节跳动系统设计面试实时排行榜

题目 现在有个游戏&#xff0c;有亿级别的用户会来玩&#xff0c;玩游戏后会获得一个分数&#xff0c;当游戏结束的时候返回给用户自己所在的排名和排名前百分比&#xff0c;如何设计这样一个系统。 思路 功能其实并不算复杂&#xff0c;有很多种方式可以实现&#xff0c;所…

mongo aggregate统计排行榜及spring mongo实现

数据分析里排行也是很常见的一项统计&#xff0c;下面是随便弄的测试数据的租赁次数排行统计&#xff1a; 脚本&#xff1a; db.leaseorders.aggregate([ { $match: { tenantId: "5" } }, { $project: { goodsName: 1, quantity: 1 …

大规模排行榜系统实践及挑战

声明&#xff1a;本文来自腾讯增值产品部官方公众号小时光茶社&#xff0c;为CSDN原创投稿&#xff0c;未经许可&#xff0c;禁止任何形式的转载。 作者&#xff1a;唐聪&#xff0c;2014年毕业加入腾讯&#xff0c;SNG增值产品部后台开发工程师&#xff0c;一直从事QQ会员后台…

排行榜设计

最近在做排行榜功能&#xff0c;排行榜无非就是对用户一些数据的排序&#xff0c;在量级不是很大的情况下还是比较简单的&#xff0c;在数据结构上使用数组&#xff0c;set&#xff0c;map都可以&#xff0c;依具体情况而定&#xff0c;这里不做过多讨论。现在遇到的问题是在数…

通过redis实现游戏排行榜功能

需求说明 水晶数量排行榜英雄熟练度排版只排前一万名&#xff0c;玩家只能看到前200名的数据和自己的名次每个排行榜实时刷新&#xff0c;玩家可以延迟5分钟查看榜单数据&#xff0c;但是自己的名次需要尽可能实时查看分值一样&#xff0c;则先达到分值的玩家前面在前 设计说…

游戏排行榜实现mysql_游戏中百万用户排行设计与实现

排行榜在游戏中非常常见的功能之一,在游戏中有各种排行榜,如工会活跃度,玩家的英雄战斗力排行等。当数据上亿时,如果使用数据库直排是致命的慢,远远超出用户接受的响应时间。也对数据库造成非常大的压力。本文将会讲述千万用户级别的用户排行系统的一些设计理念并讲述数据…