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

前言

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

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

首先,我们需要创建一个贡献榜单,可以使用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/jsondestination: gift_all_flow
GiftFlowInput: #礼物队列content-type: application/jsongroup: GiftAllFlowGroup

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

//redis lua脚本配置
@Slf4j
@Configuration
public class RedisConfig {@Autowiredprivate 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' thenredis.call('ZADD',key,newValue,member)return 1
elseredis.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);@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate 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 {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Autowiredprivate AnchorLevelBusiness anchorLevelBusiness;private static final String cacheKey = "GIFT:TASK:INTER:EVENT:";@Autowiredprivate EeEnvironmentHolder eeEnvironmentHolder;@Autowiredprivate ActivityRoundDao activityRoundDao;@Autowiredprivate ActivityTimeCache activityTimeCache;@Autowiredprivate GiftConfigCache giftConfigCache;@Autowiredprivate GiftFlowProcessor giftFlowProcessor;@Autowiredprivate AnchorCache anchorCache;@Autowiredprivate RoundListBusiness roundListBusiness;@Autowiredprivate 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";@Autowiredprivate RankScoreUtilManager rankScoreUtilManager;@Autowiredprivate ActivityTimeCache activityTimeCache;@AutowiredRedisTemplate<String, String> redisTemplate;@Autowiredprivate 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.rhkb.cn/news/226689.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【NI-RIO入门】理解Windows、Real Time与FPGA之间数据通信的原理

于NI kb摘录 1.概述 对于NI RIO系列设备&#xff08;CompactRIO、sbRIO、myRIO等&#xff09;进行编程时&#xff0c;需要注意有三个不同的组件。 人机界面 (HMI) 。有时称为“主机”&#xff0c;为用户提供图形用户界面&#xff08;GUI&#xff09;&#xff0c;用于监控系统…

阿里云数据库polardb怎么收费?

阿里云数据库PolarDB租用价格表&#xff0c;云数据库PolarDB MySQL版2核4GB&#xff08;通用&#xff09;、2个节点、60 GB存储空间55元5天&#xff0c;云数据库 PolarDB 分布式版标准版2核16G&#xff08;通用&#xff09;57.6元3天&#xff0c;阿里云百科aliyunbaike.com分享…

搭建flink集群 —— 筑梦之路

Apache Flink 是一个框架和分布式处理引擎&#xff0c; 用于在无边界和有边界数据流上进行有状态的计算。 Flink 能在所有常见集群环境中运行&#xff0c;并能以内存速度和任意规模进行计算。 Flink并没有依靠自身实现所有分布式系统需要解决的问题&#xff0c; 而是在已有集群…

基于 Vue3 和 WebSocket 实现的简单网页聊天应用

首先附上项目介绍,后面详细解释技术细节 1. chat-websocket 一个基于Vue3和WebSocket的简易网络聊天室项目&#xff0c;包括服务端和客户端部分。 项目地址 websocket-chat 下面是项目的主要组成部分和功能&#xff1a; 项目结构 chat-websocket/ |-- server/ # WebSocket 服…

蓝桥杯c/c++程序设计——冶炼金属

冶炼金属 问题描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X&#xff0c;当普通金属 O 的数目不足 V 时&#xff0…

Linux习题1

解析&#xff1a;Tcsh是csh的增强版&#xff0c;并且完全兼容csh。它不但具有csh的全部功能&#xff0c;还具有命令行编辑、拼写校正、可编程字符集、历史记录、作业控制等功能。 AWK是一种优良的文本处理工具&#xff0c;Linux及Unix环境中现有的功能最强大的数据处理引擎之一…

C++ Qt开发:SqlRelationalTable关联表组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍SqlRelationalTable关联表组件的常用方法及灵…

纯CSS实现马里奥效果,回忆一下童年吧

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…

【Filament】立方体贴图(6张图)

1 前言 本文通过一个立方体贴图的例子&#xff0c;讲解三维纹理贴图&#xff08;子网格贴图&#xff09;的应用&#xff0c;案例中使用 6 张不同的图片给立方体贴图&#xff0c;图片如下。 读者如果对 Filament 不太熟悉&#xff0c;请回顾以下内容。 Filament环境搭建绘制三角…

安装 PyQt5 保姆级教程

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 博主之前做应用层开发用的一直是 Qt&#xff0c;这次尝试一下在 python 中使用 Pyqt5 模块来开发 UI 界面&#xff0c;这里做一些…

C语言rand函数,srand函数,time函数实现随机数,及猜数字小游戏

怀心之所爱&#xff0c;奔赴山河 前言 最近在复习c的知识&#xff0c;想起之前写过一个猜数字小游戏&#xff0c;所以今天就把自己关于随机数的使用经验分享一下&#xff0c;希望对大家有帮助。 一.rand函数 1.函数的声明如下 可以看到&#xff0c;返回值是int类型&#xff…

MCEWMDRMNDBootstrap.dll文件丢失,软件游戏无法启动,怎样下载修复

不少小伙伴反馈&#xff0c;在打开某些游戏或软件的时候&#xff0c;Windows会提示“MCEWMDRMNDBootstrap.dll文件丢失&#xff0c;软件无法启动”&#xff0c;不知道应该怎样办&#xff1f; 首先&#xff0c;我们先来了解“MCEWMDRMNDBootstrap.dll文件”是什么&#xff1f; …

Arduino stm32 USB CDC虚拟串口使用示例

Arduino stm32 USB CDC虚拟串口使用示例 &#x1f4cd;相关篇《STM32F401RCT6基于Arduino框架点灯程序》&#x1f516;本开发环境基于VSCode PIO&#x1f33f;验证芯片&#xff1a;STM32F401RC⌛USB CDC引脚&#xff1a; PA11、 PA12&#x1f527;platformio.ini配置信息&…

基于YOLOv8的遥感SAR舰船小目标识别

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文摘要&#xff1a;基于YOLOv8的遥感SAR舰船小目标&#xff0c;阐述了整个数据制作和训练可视化过程 1.YOLOv8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的…

最新AI绘画Midjourney绘画提示词Prompt教程

一、Midjourney绘画工具 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭…

Could not resolve com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.28.

1、首先进入阿里云maven仓库&#xff0c;在搜索栏输入无法下载的依赖名称&#xff0c;查询现有版本号&#xff0c;可以看到这里有2.9.34。 2、在build.gradle(Project)的buildscript闭包下替换为阿里云maven仓库&#xff1a; maven { url https://www.jitpack.io } maven { u…

3D游戏角色建模纹理贴图处理

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 在本文中&#xff0c;我们将介绍 3D 纹理的基础知识&#xff0c;并讨…

算法设计与分析实验报告-贪心算法

校课程的简单实验报告。 算法设计与分析实验报告-递归与分治策略 算法设计与分析实验报告-动态规划算法 算法设计与分析实验报告-贪心算法 dijkstra迪杰斯特拉算法&#xff08;邻接表法&#xff09; 算法设计与分析实验报告-回溯法 算法设计与分析实验报告-分支限界法 …

Spring高手之路-@Autowired和@Resource注解异同点

目录 相同点 不同点 1.来源不同。 2.包含的属性不同 3.匹配方式&#xff08;装配顺序&#xff09;不同。 ​编辑 4.支持的注入对象类型不同 5.应用地方不同 相同点 都可以实现依赖注入&#xff0c;通过注解将需要的Bean自动注入到目标类中。都可以用于注入任意类型的Bean…

什么是uniapp?用uniapp开发好不好用?

随着移动应用市场的持续发展&#xff0c;开发者们面临着不断增长的需求和多样化的平台选择。在这个背景下&#xff0c;UniApp 应运而生&#xff0c;成为一种跨平台开发框架&#xff0c;为开发者提供了一种高效、简便的方式来开发移动应用程序。利用 UniApp 开发应用程序可以节省…