Redis实现日榜|直播间榜单|排行榜|Redis实现日榜01

news2025/2/3 20:56:45

前言

直播间贡献榜是一种常见的直播平台功能,用于展示观众在直播过程中的贡献情况。它可以根据观众的互动行为和贡献值进行排名,并实时更新,以鼓励观众积极参与直播活动。

在直播间贡献榜中,每个观众都有一个对应的贡献值,贡献值用来衡量观众在直播过程中的贡献程度。观众的贡献值可以通过多种途径获得,比如送礼物、打赏主播等。

首先,我们需要创建一个贡献榜单,可以使用Redis的有序集合 (Sorted Set)结构来实现。在有序集合中,每个观众对应一个唯一的ID作为成员,而成员的分数表示观众的贡献值。可以根据观众每次送出礼物增加相应的贡献值。

当有新的观众参与直播并进行互动时,我们可以使用ZADD命令将其用户ID添加到贡献榜单中,并更新相应的贡献值。可以根据贡献值对观众进行排序,从而得到当前排名靠前的观众。

要实时更新贡献榜单,可以使用ZINCRBY命令增加观众的贡献值。当观众进行互动行为时,我们可以调用ZINCRBY命令增加相应观众的贡献值,并确保贡献榜单及时反映观众的最新贡献情况。

Redis实现命令

用户ID为Test1000的得到价值为1314的礼物时,以及获取排行榜时,命令如下。比如

# 增加排行榜用户数据ZINCRBY ROUND_LIST_CACHE_20221222 1314 Test1000# 展示用户榜单ZRANGE ROUND_LIST_CACHE_20221222 0 -1 WITHSCORES

JAVA简单逻辑代码实现 

1.Spring boot的yml配置文件,配置礼物队列

       
#yml配置文件配置队列 
GiftFlowOutput: 
  content-type: application/json
  destination: gift_all_flow
GiftFlowInput: #礼物队列
  content-type: application/json
  group: GiftAllFlowGroup

2.redis使用lua脚本增加榜单,保证多机并发原子性

//redis lua脚本配置
@Slf4j
@Configuration
public class RedisConfig {

    @Autowired
    private JdkCacheHandler jdkCacheHandler;


    @Bean("zsetScoreScript")
    public RedisScript<Long> zsetScoreScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/zadd.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
}

3。LUA脚本具体实现,保留3位有效礼物小数位,后面小数位用于同个时间刷礼物进行排序,目前这里只精确到了秒

local key=KEYS[1]
local member=KEYS[2]
local newValue=tonumber(string.format("%.16f",ARGV[1]))
local oldValue=redis.call('ZSCORE',key,member)
if type(oldValue) == 'boolean' then
    redis.call('ZADD',key,newValue,member)
    return 1
else
    redis.call('ZADD',key,tonumber(string.format("%.3f",oldValue))+newValue,member)
    return 1
end
return 0

 4.调用lua脚本,增加排行榜积分

@Component
@Slf4j
public class RankScoreUtilManager {

    private final static DecimalFormat format = new DecimalFormat(ActivityBase.TOTAL_FORMAT);

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ActivityTimeCache activityTimeCache;

    @Resource(name = "zsetScoreScript")
    private RedisScript<Long> zaddScript;

    /**
     * 添加分数到排行榜,可以并发的
     */
    public void addScoreToRank(String cacheKey, String anchorId, BigDecimal integral, Date eventTime) {
        try {
            BigDecimal bigDecimal = dealScore(integral, activityTimeCache.getActivityDTO().getEndTime(), eventTime);
            String score = format.format(bigDecimal.doubleValue());
            Long execute = redisTemplate.execute(zaddScript, Arrays.asList(cacheKey, anchorId), score);
            log.warn("增加积分到排行榜integral={},anchorId={},score={},execute",integral,anchorId,score,execute);
        } catch (Exception e) {
            log.error("增加异常", e);
        }
    }

    private static BigDecimal dealScore(BigDecimal newScore, LocalDateTime activityEndTime, Date eventDate) {
        DecimalFormat format = new DecimalFormat(ActivityBase.VALID_FORMAT);
        String formatStr = format.format(EeBigDecimalUtil.scale(newScore, ActivityBase.VALID_SCALE, RoundingMode.DOWN).doubleValue());
        StringBuilder sb = new StringBuilder(32);
        //后面补个0,避免lua进1出错
        sb.append(formatStr).append('0');
        long n = EeDateUtil.getMilli(activityEndTime) - eventDate.getTime();
        String s = Long.toString(Math.abs(n) / 1000);
        for (int i = s.length(); i < ActivityBase.TIME_SCALE; i++) {
            sb.append('0');
        }
        sb.append(s);
        return new BigDecimal(sb.toString()).setScale(ActivityBase.TOTAL_SCALE, RoundingMode.DOWN);
    }

}

5.配置礼物队列名称 

/**
* 监听礼物流水队列
*/
public interface AllGiftFlowProcessor {

    String OUTPUT = "GiftFlowOutput";

    @Output(OUTPUT)
    MessageChannel output();

    String INPUT = "GiftFlowInput";

    @Input(INPUT)
    SubscribableChannel input();
}

 6.监听礼物队列的listener,前面做了一些活动时间校验的判断,最关键的是最下面roundListBusiness.dealAnchorRoundList(dto);的方法


//监听礼物队列,处理相关业务逻辑,榜单的处理在最下面

@Slf4j
@Service
public class AllGiftFlowListener {


    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @Autowired
    private AnchorLevelBusiness anchorLevelBusiness;

    private static final String cacheKey = "GIFT:TASK:INTER:EVENT:";

    @Autowired
    private EeEnvironmentHolder eeEnvironmentHolder;


    @Autowired
    private ActivityRoundDao activityRoundDao;

    @Autowired
    private ActivityTimeCache activityTimeCache;

    @Autowired
    private GiftConfigCache giftConfigCache;
    @Autowired
    private GiftFlowProcessor giftFlowProcessor;

    @Autowired
    private AnchorCache anchorCache;

    @Autowired
    private RoundListBusiness roundListBusiness;

    @Autowired
    private EeLog eeLog;



    @StreamListener(AllGiftFlowProcessor.INPUT)
    public void onReceive(ActivityGiftEventDTO dto) {
        MqConsumeRunner.run(dto.getEventId().toString(), dto, o -> dealMsgEvent(o), "TaskIntegralProcessor [{}]", dto);
    }

    private void dealMsgEvent(ActivityGiftEventDTO dto) {

        // 过滤非活动时间礼物
        ActivityDTO activityDTO = activityTimeCache.getActivityDTO();
        if (null == activityDTO) {
            return;
        }
        if (EeDateUtil.toLocalDateTime(dto.getEventDate()).isBefore(activityDTO.getStartTime())) {
            eeLog.info("礼物时间小于活动开始时间,丢弃礼物");
            return;
        }

        // 判断活动时间
        if (ActivityStatusEnum.NO_START == activityRoundDao.getActivityStatus()) {
            return;
        }

        // 过滤活动礼物
        if (giftConfigCache.getData().stream().noneMatch(o -> o.getGiftId().equals(dto.getGiftId()))) {
            eeLog.info("礼物id:{}不计算", dto.getGiftId());
            return;
        }

        Integer region = anchorCache.getRegionById(dto.getTarget());
        // 是否为签区域主播
        if (null == region || !ActivityBase.AnchorRegion.contains(region)) {
            eeLog.warn("该主播非签约或非参赛区域:{}", dto.getTarget());
            return;
        }

        // 是否重复消费礼物
        Boolean success = redisTemplate.opsForValue().setIfAbsent(cacheKey + dto.getEventId(), "", 15, TimeUnit.DAYS);
        if (success != null && !success) {
            eeLog.info("升级事件已处理:" + dto);
            return;
        }

        try {
            //监听礼物并且处理榜单(最主要的代码就这一句)
            roundListBusiness.dealAnchorRoundList(dto);
        } catch (Exception e) {
            log.error("处理榜单 fail.[" + dto + "]", e);
        }


    }

}

 7.榜单的具体实现逻辑

@Component
@Slf4j
public class RoundListBusiness {

    //平台主播榜单
    private final static String CHRISTMAS_ROUND_ANCHOR_LIST = "CHRISTMAS:ROUND:ANCHOR:LIST";
    

    private final static String CHRISTMAS_ROUND_LIST_LOCK = "CHRISTMAS:ROUND:LIST:LOCK";


    @Autowired
    private RankScoreUtilManager rankScoreUtilManager;

    @Autowired
    private ActivityTimeCache activityTimeCache;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @Autowired
    private AllGiftFlowProcessor allGiftFlowProcessor;
    

    /**
    * 处理榜单加分逻辑
    */
    public void dealAnchorRoundList(ActivityGiftEventDTO dto) {

        ActivityDTO activityDTO = activityTimeCache.getActivityDTO();
        if (EeDateUtil.toLocalDateTime(dto.getEventDate()).isBefore(activityDTO.getStartTime())) {
            return;
        }

        if (!EeDateUtil.toLocalDateTime(dto.getEventDate()).isBefore(activityDTO.getEndTime())) {
            return;
        }


        //记录总的榜单流水
        try {
            //插入总的流水
            allGiftFlowProcessor.output().send(MessageBuilder.withPayload(dto).build());
        } catch (Exception e) {
            log.error("插入总的礼物流水异常dto={}", dto, e);
        }

        LocalDateTime now = LocalDateTime.now();
        if (!now.isBefore(activityDTO.getEndTime())) {
            //2.判断是否符合处理上一轮榜单的逻辑
            if (isThrowAwayBeforeGift(dto.getEventId(), now, activityDTO.getEndTime())) {
                log.warn("这里跳出了dto={},now={}", dto, EeDateUtil.format(now));
                return;
            }
        }

        dealRoundList(dto, dto.getTotalStarAmount());
    }

   

    /**
    * 处理主播榜单加分逻辑
    */
    private void dealRoundList(ActivityGiftEventDTO dto, BigDecimal value) {
        //增加平台主播榜单
        incrAnchorListValue(CHRISTMAS_ROUND_ANCHOR_LIST, dto.getTarget(), value, dto.getEventDate());
    }

    
    /**
    * 具体加分方法
    */
    public void incrAnchorListValue(String listCacheKey, String userId, BigDecimal value, Date eventTime) {
        if (EeStringUtil.isNotEmpty(listCacheKey)) {
            //增加榜单分数
            rankScoreUtilManager.addScoreToRank(listCacheKey, userId, value, eventTime);
        }
    }
    
    /**
    * 判断是否已经超过结算时间
    */
    private boolean isThrowAwayBeforeGift(String eventId, LocalDateTime now, LocalDateTime endTime) {
        //如果当前时间超过了结算时间,直接丢弃礼物
        if (!now.isBefore(endTime.plusSeconds(ActivityBase.PROCESS_TS))) {
            log.error("主播榜单-当前时间超过了结算时间,直接丢弃礼物: {}", eventId);
            return true;
        }

        //如果上一轮的榜单已经锁定,丢弃礼物
        if (checkBlockRankList(CHRISTMAS_ROUND_ANCHOR_LIST)) {
            log.error("主播榜单-榜单被锁定后丢弃礼物: {}, {}", eventId, EeDateUtil.format(LocalDateTime.now()));
            return true;
        }
        return false;
    }

    /**
    * 判断结算时榜单是否已经被锁定
    */
    public boolean checkBlockRankList(String listCacheKey) {
        Boolean cache = redisTemplate.opsForHash().hasKey(CHRISTMAS_ROUND_LIST_LOCK, listCacheKey);
        return null != cache && cache;
    }

    /**
     * 锁定榜单,把锁定的榜单都放入一个hash中
     */
    public void setBlockRankList(String cacheKey) {
        redisTemplate.opsForHash().put(CHRISTMAS_ROUND_LIST_LOCK, cacheKey, EeDateUtil.format(LocalDateTime.now()));
    }
}

总结:目前这段代码只是实现了简单的日榜逻辑,还有一段结算的代码我没有复制出来,结算榜单无非就是在每天0点的时候结算前一天的榜单,对榜单前几名的主播进行礼物发放,后续将会更新几种复杂榜单的实现方式,包括:晋级榜单,积分晋级榜单,滚动日榜,滚动周榜,滚动月榜的一些实现方式

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

这样使用云渲染又快又省钱

我们都知道使用云渲染是要钱的&#xff0c;而且渲染的时间越久&#xff0c;需要的渲染费越多&#xff0c;哪么如何又快又省钱的拿到效果图呢&#xff1f;用炫云的渲染质量&#xff0c;保准让你使用云渲染渲染效果图又快又省钱。 我们使用炫云的时候&#xff0c;根据自己的需求…

输入框获取焦点

Entry Component struct Test {build() {Row() {Column({ space: 5 }) {//Text("自定义样式").customStyles(20,Color.Yellow).backgroundColor("#36D").padding(10).borderRadius(30)TextInput({placeholder: "获取焦点"}).borderColor(Color.Y…

LLM微调(四)| 微调Llama 2实现Text-to-SQL,并使用LlamaIndex在数据库上进行推理

Llama 2是开源LLM发展的一个巨大里程碑。最大模型及其经过微调的变体位居Hugging Face Open LLM排行榜&#xff08;https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard&#xff09;前列。多个基准测试表明&#xff0c;就性能而言&#xff0c;它正在接近GPT-3.5…

图灵日记之java奇妙历险记--数据类型与变量运算符

目录 数据类型与变量字面常量数据类型变量语法格式整型变量浮点型变量字符型变量希尔型变量类型转换自动类型转换(隐式)强制类型转换(显式) 类型提升不同数据类型的运算小于4字节数据类型的运算 字符串类型 运算符算术运算符关系运算符逻辑运算符逻辑与&&逻辑或||逻辑非…

案例125:基于微信小程序的个人健康数据管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

【论文阅读】FreeU: Free Lunch in Diffusion U-Net

FreeU: 无需训练直接提升扩散模型生成效果。 paper&#xff1a;https://arxiv.org/abs/2309.11497 code&#xff1a;GitHub - ChenyangSi/FreeU: FreeU: Free Lunch in Diffusion U-Net 1. 介绍 贡献&#xff1a; •研究并揭示了U-Net架构在扩散模型中去噪的潜力&#xff0…

目标检测入门体验,技术选型,加载数据集、构建机器学习模型、训练并评估

Hi, I’m Shendi 1、目标检测入门体验&#xff0c;技术选型&#xff0c;加载数据集、构建机器学习模型、训练并评估 在最近有了个物体识别的需求&#xff0c;于是开始学习 在一番比较与询问后&#xff0c;最终选择 TensorFlow。 对于编程语言&#xff0c;我比较偏向Java或nod…

vue关闭当前路由页面并跳转到其父页面

1.dom中添加关闭或取消按钮 <el-button type"primary" class"blueLinearbg cancelBtn" click"cancel" >取 消</el-button>2.cancel方法中 /*取消或关闭*/cancel(){this.$store.dispatch("tagsView/delView", this.$route)…

state的保留与重置

让组件状态保留的情况&#xff1a; 让组件状态重置的3种情况&#xff1a;

[NISACTF 2022]easyssrf

[NISACTF 2022]easyssrf wp ssrf 的题目&#xff0c;提示了会使用 curl 连接输入的网站并返回响应包。 测试连接百度 直接在输入框中写 www.baidu.com 是无法连接的&#xff0c;需要在前面加入 http 或者 https &#xff0c;因为 curl 的使用方式就是&#xff1a; curl htt…

从0到1部署gitlab自动打包部署项目

本文重点在于配置ci/cd打包 使用的是docker desktop 第一步安装docker desktop Docker简介 Docker 就像一个盒子&#xff0c;里面可以装很多物件&#xff0c;如果需要某些物件&#xff0c;可以直接将该盒子拿走&#xff0c;而不需要从该盒子中一件一件的取。Docker中文社区、…

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

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 man命令在Ubuntu系统中是一个非常重要的命令&#xff0c;它用于查看系统的手册页。手册页是Linux和Unix系统中的一种在线文档&#xff0c;用于描述系统中的命令、函数、配置文件等的详细信息。 man命令的基本格式是 man [选项] …

光伏企业如何能够提高光伏电站的建设效率?

随着全球对可再生能源需求的日益增长&#xff0c;光伏行业的发展势头强劲。然而&#xff0c;光伏电站建设过程中往往存在效率低下的问题&#xff0c;这不仅影响了电站的运营成本&#xff0c;也制约了整个行业的发展速度。因此&#xff0c;如何提高光伏电站的建设效率&#xff0…

DshanMCU-R128s2 Hello World!

本文将介绍使用 R128 开发板从串口输出 Hello World 的方式介绍 SDK 软件开发流程。 载入方案 我们使用的开发板是 R128-Devkit&#xff0c;需要开发 C906 核心的应用程序&#xff0c;所以载入方案选择r128s2_module_c906 $ source envsetup.sh $ lunch_rtos 1编辑程序 打…

Windows 系统下本地单机搭建 Redis(一主二从三哨兵)

目录 一、Redis环境准备&#xff1a; 1、下载redis 2、Windows下的.msi安装和.zip格式区别&#xff1a; 二、哨兵介绍&#xff1a; 1、一主二从三哨兵理论图&#xff1a; 2.哨兵的主要功能&#xff1a; 3.哨兵用于实现 redis 集群的高可用&#xff0c;本身也是分布式的&…

锐捷配置PVLAN

一、实验拓扑 二、实验目的 PVLAN可以通过主VLAN和辅助VLAN的概念&#xff0c;部署隔离技术&#xff0c;实现用户间的互访控制。 三、实验配置 SW2 Ruijie >enable Ruijie #configure terminal Ruijie (config)#vlan 20 Ruijie (config-vlan)#private-vlan community …

喜报|迪捷软件“ModelCoder 建模及形式化验证代码生成软件”荣登浙江省首版次产品目录

近日&#xff0c;浙江省经济和信息化厅公布《2023年浙江省首版次软件产品应用推广指导目录》&#xff0c;浙江迪捷软件科技有限公司的“ModelCoder 建模及形式化验证代码生成软件”经过多轮审核及专家评定被纳入目录&#xff0c;这是迪捷软件自主研发的产品继“天目全数字实时仿…

嵌入式科普(5)ARM GNU Toolchain相关概念和逻辑

一、目的/概述 二、资料来源 三、逻辑和包含关系 四、Arm GNU Toolchain最常用的命令 嵌入式科普(5)ARM GNU Toolchain相关概念和逻辑 一、目的/概述 对比高集成度的IDE(MDK、IAR等)&#xff0c;Linux开发需要自己写Makefile等多种脚本。eclipse、Visual Studio等需要了解预处…

冬天天冷早安问候语关心话,愿我的每句话都能带给你温馨

1、送你一声问候&#xff0c;为你驱走冬日严寒&#xff0c;送你一份关怀&#xff0c;为你增添丝丝温暖&#xff0c;送你一句祝福&#xff0c;为你驱走所有不快&#xff0c;送你一份关爱&#xff0c;为你增添幸福无限&#xff0c;天虽寒了&#xff0c;我的关心犹在&#xff0c;愿…

一分钟学会“沉浸式翻译”插件的安装与使用

一、安装 安装地址&#xff1a;https://immersivetranslate.com/ 选择对应的浏览器进入安装即可 二、简单的翻译使用方法 第一次安装需要先刷新界面才可以达到翻译效果 核心需要修改的地方在以下三个&#xff1a; 第一处&#xff1a;设置翻译服务&#xff0c;免费版的可以直接…