Redis实现日榜|晋级榜单|直播间榜单|排行榜|Redis实现日榜02

目录

前言

难点

解决方案


前言

通常一个主播的活动榜单大概会分为几个流程来进行,例如可以分为海选赛,晋级赛,突围赛,年度10大主播,年度总决赛。

1.海选赛:从平台所有的主播中进行选拔,在海选赛比赛期间,前60可以晋级到下一轮比赛中,60名以外的主播被淘汰。

2.晋级赛:从海选赛晋级的60名主播中,继续持续几天比赛,最终在60名主播中的前40名主播晋级到下一轮比赛中。

3.突围赛:40晋级20。

4.年度10大主播:20名主播在比赛阶段中的前10晋级到总决赛成为年度10大主播。

5.总决赛:决出最终的冠亚季军。

其实每个赛段中还会有一些其他的逻辑,比如:复活和保送逻辑。从上个轮次中淘汰的主播可以通过复活赛进行复活,或者通过其他活动可以获得晋级赛的保送名额,由于本篇文章主要讲解榜单的晋级逻辑,所以这里就暂且先不详细说明复活和保送的逻辑。

难点

通常比赛都是以每天0点进行当天榜单的结算,如果一个赛段持续2天,那就是第二天的2点会进行榜单的结算,以N进60的海选赛为例:6号和7号两天的前60名晋级到下一轮,晋级榜单会在8号的0点进行结算。假如在7号要到结算时间的时候用户来冲榜,导致很多礼物堆积在队列中,会导致在8号0点那一刻不能马上结算出前60名的榜单(因为队列中的礼物可能会存在没消费完的情况),所以这里需要有个结算时间,这就是为什么很多直播平台每天榜单结算的时候会有结算倒计时的原因!这里假设我们结算是从8号的0点开处理结算逻辑,结算时间为10s,那也就是8号的0点到8号的0点0分10秒榜单会处在结算中的状态,但是这个时间段60进40的赛程已经开始,可是晋级的60名榜单要在10s之后才能结算出来,那么在结算中0s-10s中60晋级40的榜单要怎么生成?

解决方案

大概的榜单实现逻辑已经在上一篇Redis实现日榜|直播间榜单|排行榜|Redis实现日榜01中提到了,所以本篇主要介绍结算的逻辑和榜单合并的逻辑。

/**
     * 榜单结算定时任务
     */
    @Scheduled(cron = "8 0 0 * * ?")
    @EeSingleEntry(key = "guild:match:rank_lock", expire = 2)
    public void anchorRank() {
        ActivityDTO activityDTO = activityTimeCache.getActivityDTO();
        LocalDateTime now = LocalDateTime.now();
        if (EeObjectUtil.isNotEmpty(activityDTO) && now.isAfter(activityDTO.getStartTime())) {
            //处理日榜
            EeAsyncUtil.runAsync(() -> {
                //获取当前进行的轮次
                RoundTimeEntity beforeRound = roundTimeService.getGuildBeforRound(now);
                if (EeObjectUtil.isNotEmpty(beforeRound)) {
                    if (now.isAfter(beforeRound.getEndTime()) && now.isBefore(beforeRound.getEndTime().plusSeconds(ActivityBase.PROCESS_TS))) {
                        log.info("定时任务开始执行---now={}", EeDateUtil.format(now));
                        dealGuildMatchRankList(beforeRound);
                    }
                }
            }, executor);
        }
    }
/**
     * 处理对应轮次的榜单结算
     */
    public void dealGuildMatchRankList(RoundTimeEntity beforeRound) {

        if (EeObjectUtil.isNotEmpty(beforeRound)) {
            if (beforeRound.getRoundId() == ONE) {
                //n进10
                dealOneRankList(beforeRound);
            } else if (beforeRound.getRoundId() == TWO) {
                //n进5
                dealTwoRankList(beforeRound);
            } else if (beforeRound.getRoundId() == THREE) {
                //8大公会
                dealThreeList(beforeRound);
            } else if (beforeRound.getRoundId() == FOUR) {
                //总决赛
                dealFinalList(beforeRound);
            }
        }
    }

 进行N进60的结算的时候,判断榜单是否锁定,如果没锁定就进行结算,榜单锁定后,礼物队列再次受到前一日的礼物消息的时候,会进行丢弃处理,然后处理紧急榜单和初始化下一轮的榜单,最后结算完成后进行榜单的合并处理。

/**
     * N进60
     */
    private void dealOneRankList(RoundTimeEntity beforeRound) {
        //获取榜单的缓存key
        String matchRoundListName = guildMatchValueBusiness.getGuildMatchRoundListCacheName(beforeRound.getRoundId());
        //判断榜单是否已经锁定
        if (!guildMatchValueBusiness.checkGuildBlockRankList(matchRoundListName)) {
            //先锁定榜单
            guildMatchValueBusiness.setGuildBlockRankList(matchRoundListName);

            //获取榜单中前60排名
            List<AssociationListDTO> rankList = guildMatchValueBusiness.getAssociationList(matchRoundListName, 0, 59);
            if (CollectionUtils.isNotEmpty(rankList)) {
                for (AssociationListDTO association : rankList) {
                    //插入下一个轮次的晋级榜单,这里主要是用于存储晋级的公会,没实际作用
                    guildMatchValueBusiness.setPromotionList(TWO, association.getAssociationId());
                    //初始化下一个轮次的晋级榜单,这个才是真正的榜单
                    guildMatchValueBusiness.initializationNexDayList(String.valueOf(association.getAssociationId()), 3, BigDecimal.ZERO);

                    //发送晋级弹窗
                    sendPromotionPopUps(association.getAssociationId(), association.getRank(), beforeRound.getRoundId());
                }
                //发送未晋级弹窗
                sendNoPromotionPopUps(beforeRound.getRoundId(), rankList.stream().map(AssociationListDTO::getAssociationId).collect(Collectors.toList()));
            }

            //处理完晋级名单后锁定临时榜单,并且把临时榜单数据copy到正式榜单中
            guildMatchValueBusiness.setTemporaryBlockRankList(beforeRound.getRoundId() + 1);
        }
    }

这里处理榜单积分的时候,和简单日榜有点区别,判断如果当前是结算时间的时候,要把所有用户的分支都加到临时榜单中

/**
     * 处理榜单积分
     */
    public boolean doTwoProcessor(BigDecimal value, Integer associationId, RoundTimeEntity nowRound, String anchorId, LocalDateTime eventTime) {

        //在结算时间之内 没有锁定要增加榜单数据到临时榜单中,为了兼容0-10s结算中,晋级名单还没出来的情况
        if (isOrNotAddTemporaryRoundList(nowRound)) {
            incrTemporaryRoundListValue(associationId.toString(), value.doubleValue(), nowRound.getRoundId(), anchorId);
        } else {
            //如果公会没晋级不需要处理
            if (!checkPromotion(nowRound.getRoundId(), associationId)) {
                log.warn("第三轮当前公会已经淘汰roundId={},associationId={}", nowRound.getRoundId(), associationId);
                return false;
            }
            //增加公会赛榜单积分
            incrRoundListValue(associationId.toString(), value.doubleValue(), nowRound.getRoundId(), anchorId, eventTime);
        }
        return true;
    }

 结算完成后,合并临时榜单到正式榜单的逻辑

/**
     * 锁定临时榜单,数据写入正常日榜中
     */
    public void setTemporaryBlockRankList(int roundId) {
        //锁定榜单后把临时榜单中的内容复制到正式榜单中
        log.warn("插入晋级名单成功now={},roundId={},region={},groupId={}", EeDateUtil.format(LocalDateTime.now()), roundId);

        //临时榜单key
        String temporaryName = getTemporaryRoundListCacheName(roundId);
        redisTemplate.opsForHash().put(GUILD_TEMPORARY_LIST_BLOCK, temporaryName, EeDateUtil.format(LocalDateTime.now()));
        //今日榜单的key
        String todayName = getGuildMatchRoundListCacheName(roundId);

        //获取到已经初始化好的晋级榜单
        Set<ZSetOperations.TypedTuple<String>> rankByRangeWithScores = getRankByRangeWithScores(todayName, 0, -1);
        if (CollectionUtils.isNotEmpty(rankByRangeWithScores)) {
            for (ZSetOperations.TypedTuple data : rankByRangeWithScores) {
                //获取到临时榜单中的值,incr到正式榜单中
                Double score = redisTemplate.opsForZSet().score(temporaryName, data.getValue().toString());
                if (EeObjectUtil.isNotEmpty(score)) {
                    //合并今日榜单
                    redisTemplate.opsForZSet().incrementScore(todayName, data.getValue().toString(), score);
                }
            }
        }
    }

晋级榜单最重要的就是在每天日榜结算的过程中会出现第二天榜单还没结算出来的情况,所以在结算期间还是要计算所有人的一个临时榜单,最后结算完成后,把临时榜单中已经晋级的主播分数合并到正常榜单中就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/264644.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu 常用命令之 cal 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 cal命令在Ubuntu系统下用于显示日历。它可以显示任何特定月份或整个年份的日历。 cal命令的参数如下 -1&#xff1a;只显示当前月份的日历。-3&#xff1a;显示前一个月、当前月和下一个月的日历。-s&#xff1a;指定日历的开始…

uni-app学习记录

uni-app官网学习记录 uni-app注意点记录 页面跳转注意事项 navigateTo, redirectTo 只能打开非 tabBar 页面。switchTab 只能打开 tabBar 页面。reLaunch 可以打开任意页面。不能在首页 onReady 之前进行页面跳转。 页面通讯 // 发起页面uni.$emit(update,{msg:页面更新})//…

4G微型RTU如何实现冬季工业管网远程监测

随着我国北方全面进入到冬季&#xff0c;多日以来严寒、降雪天气频发&#xff0c;工业基础设施也迎来冬季考验。对于一些输送化工原料、油气和给排水等用途的工业管网设施&#xff0c;在面临极端冰雪天气时易产生各种风险&#xff0c;诸如管道水/气泄漏损耗、低温冻裂、积雪压塌…

ElasticSearch 数据分片

一、ElasticSearch 分片 ElasticSearch集群中有许多个节点(Node)&#xff0c;每一个节点实例就是一个实例&#xff1b;数据分布在分片之间。集群的容量和性能主要取决于分片如何在节点上如何分配。将数据分片是为了提高可处理的容量和易于进行水平扩展&#xff0c;为分片做副本…

Unity | HybridCLR 热更新(Windows端)

目录 一、准备工作 1.环境相关 2.Unity中配置 二、热更新 1.创建 HotUpdate 热更新模块 2.安装和配置HybridCLR 3.配置PlayerSettings 4.创建热更新相关脚本 5.打包dll 6.测试热更新 一、准备工作 1.环境相关 安装git环境。Win下需要安装visual studio 2019或更高版…

为实体服务器配置Ubuntu

简介 我们在使用虚拟机时&#xff0c;直接在网上找到镜像然后下载到本地&#xff0c;在VMware创建实例时将该iso文件作为镜像源然后进行基础配置就可以轻松安装配置好Linux虚拟机。 在为实体服务器安装Linux系统&#xff0c;同样的&#xff0c;我们也需要镜像源&#xff08;即…

持续集成交付CICD:GitLabCI 封装Python类 并结合 ArgoCD 完成前端项目应用发布

目录 一、实验 1. 环境 2. Python代码实现获取文件 3.Python代码实现创建文件 4.Python代码实现更新文件 5.GitLab更新库文件与运行流水线 6.ArgoCD 完成前端项目应用发布 二、问题 1.Python获取GitLab指定仓库文件报错 2. K8S master节点运行Python代码报错 一、实验…

深度剖析Ajax实现方式(原生框架、JQuery、Axios,Fetch)

Ajax学习 简介&#xff1a; ​ Ajax 代表异步 JavaScript 和 XML&#xff08;Asynchronous JavaScript and XML&#xff09;的缩写。它指的是一种在网页开发中使用的技术&#xff0c;通过在后台与服务器进行数据交换&#xff0c;实现页面内容的更新&#xff0c;而无需刷新整个…

Halcon 检测焊点短路

Halcon 检测焊点短路 read_image (Image1, D:/image/bilibili/photo/检测焊接短路 (4).bmp) dev_close_window () dev_open_window (0, 0, 512, 512, black, WindowHandle) dev_display (Image1) set_display_font (WindowHandle, 16, mono, true, false) threshold (Image1, …

kindeditor The method toJSONString() is undefined for the type JSONObject

kindeditor 插件上传文件出错的 json_simple-1.1.jar 也不知道是多老的项目&#xff0c;多老的包了&#xff0c;稀有东西

基于SSM的剧本杀预约系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的剧本杀预约系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Sp…

日志服务 SLS 深度解析:拥抱云原生和 AI,基于 SLS 的可观测分析创新

云布道师 10 月 31 日&#xff0c;杭州云栖大会上&#xff0c;日志服务 SLS 研发负责人简志和产品经理孟威等人发表了《日志服务 SLS 深度解析&#xff1a;拥抱云原生和 AI&#xff0c;基于 SLS 的可观测分析创新》的主题演讲&#xff0c;对阿里云日志服务 SLS 产品服务创新以…

使用 Elasticsearch 检测抄袭 (一)

作者&#xff1a;Priscilla Parodi 抄袭可以是直接的&#xff0c;涉及复制部分或全部内容&#xff0c;也可以是释义的&#xff0c;即通过更改一些单词或短语来重新表述作者的作品。 灵感和释义之间是有区别的。 即使你得出类似的结论&#xff0c;也可以阅读内容&#xff0c;获得…

罗技鼠标驱动下载地址

罗技鼠标驱动下载地址 Logitech G HUB Advanced Gaming Software, RGB & Game Profiles

Flutter 三: Dart

1 数据类型 数字(number) int double 字符串转换成 num int.parse(“1”) double.parse(“1”);double 四舍五入保留两位小数 toStringAsFixed(2) 返回值为stringdouble 直接舍弃小数点后几位的数据 可使用字符串截取的方式 字符串(string) 单引号 双引号 三引号三引号 可以输…

windos/ubuntu20.4下UE4.27.2像素流送

windows/ubuntu20.4下UE4.27.2像素流送 像素流送技术可以将服务器端打包的虚幻引擎应用程序在客户端的浏览器上运行&#xff0c;用户可以通过浏览器操作虚幻引擎应用程序&#xff0c;客户端无需下载虚幻引擎&#xff0c;本文实现两台机器通过物理介质网线实现虚幻引擎应用程序…

解决xcode 运行不老iPhone 15 iOS 17.1 设备的问题

问题 最近要查看一下ios 17.1的设备的性能&#xff0c;但是当前版本的Xcode运行不了 解决方法 1、更新Xcode版本到15.1以上 2、更新完成后&#xff0c;大概率出现这个情况 原因&#xff1a;在app Store中更新到Xcode15后,运行不了模拟器和真机.需要下载iOS 17对应的模拟器.&…

层次分析法

层次分析法主要用于解决评价类问题(例如选择哪种方案最好&#xff0c;哪位运动员或者员工表现的更优秀) 先用一道引出层次分析法的例题&#xff1a;小明同学高考填完志愿后&#xff0c;小明想出去旅游。在查阅了网上的攻略后&#xff0c;他初步选择了苏杭、北戴河和桂林三地之一…

使用Aspose.Slides 控件,在线将 ODP 转换为 PPT

OpenOffice 等开源生产力工具有其用途。但如果您希望在线将 ODP 转换为 PPT&#xff0c;您很可能已经确定 Microsoft PowerPoint 的专有 PPT 格式和平台比 OpenOffice ODP 更适合您的需求。 本文的第一部分重点介绍在线将 ODP 转换为 PPT 的快速方法。第二部分探讨涉及C#应用程…

Redis设计与实现之AOF

一、AOF Redis 分别提供了 RDB 和 AOF 两种持久化机制: RDB 将数据库的快照(snapshot)以二进制的方式保存到磁盘中。 AOF 则以协议文本的方式&#xff0c;将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件&#xff0c;以此达到记录数据库状态的目的。 本章首先介绍…