SpringBoot中使用Redis实现排行榜功能,并考虑到 当用户积分相同时,要求按最后更新时间升序

Redis 实现排行榜主要依赖于其有序集合zset(Sorted Set)数据结构。

zset中可以存储不重复的元素集合,并为每个元素关联一个浮点数分数(score),Redis 会根据这个分数自动对集合中的元素进行排序。

可以使用 `ZADD` 命令来向有序集合中添加元素,将上面列表中:用户id作为元素、积分作为分数

获取用户积分排行
使用 `ZREVRANGE` 命令(从高到低排序)或 `ZRANGE` 命令(从低到高排序)来获取排行榜的前几名

# 积分相同时,按最后更新时间升序,解决思路

可以将zset中的score设置为一个浮点数,其中整数部分为积分,小数部分为最后更新时间时间戳,算法如下

## score = 积分 + 时间戳/10的13次方

> 这里为什么要除以10的13次方?由于时间戳的长度是13位,除以10的13次方,可以将其移到小数点的右边

对上面表格,处理之后,变成了下面这样

| 用户id | 积分 | 最后更新时间时间戳(毫秒) | score |
| ------ | ---- | ------------------------ | ----------------- |
| user1 | 100 | 1720663200002 | 100.1720663200002 |
| user2 | 100 | 1720663200001 | 100.1720663200001 |
| user3 | 150 | 1720663200000 | 150.1720663200000 |

按score降序排序后,是:user3 (150.1720663200000) > user1 (100.1720663200002) > user2( 100.1720663200001)

和预期的不一样,user2的最后更新时间是小于user1的,user2应该排在user1之前,怎么办呢

需要再做一次转换

## score = 积分 + (1 - 时间戳/10的13次方)

处理后,表格变成了下面这样

| 用户id | 积分 | 最后更新时间时间戳(毫秒) | score |
| ------ | ---- | ------------------------ | ----------------- |
| user1 | 100 | 1720663200002 | 100.8279336799998 |
| user2 | 100 | 1720663200001 | 100.8279336799999 |
| user3 | 150 | 1720663200000 | 150.8279336800000 |

按降序排序后,是:user3 (150.8279336800000) > user2 (100.8279336799999) > user1(100.8279336799998)

这样就达到了预期的目的

 





具体实现代码如下
1 pojo类

@Data
public class UserPointsReq {//用户idprivate String userId;//积分private Integer points;//最后更新时间(时间戳毫秒)private Long updateTime;
}/////返回结果封装
@Data
public class UserRanking {private String userId;private double redisScore;
}

2 核心代码

import com.example.demo_26.redis_paiming.dto.UserPointsReq;
import com.example.demo_26.redis_paiming.dto.UserRanking;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;/*** 用户积分排行榜控制器* <p>* 该控制器提供了用户积分的插入和查询用户积分排行榜的功能* 使用Redis的有序集合(ZSet)来存储用户积分,以实现高效查询和排序* </p>*/
@RestController
public class UserRankingController {public static final String redis_key="user:ranking";@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 用户积分批量插入到Redis** @param userPointsReqList 包含用户积分和更新时间的列表* @return 总是返回true,表示操作成功*/@PostMapping("/addUserPoint")public boolean addUserPoint(@RequestBody List<UserPointsReq> userPointsReqList) {for (UserPointsReq userPointsReq : userPointsReqList) {String userId = userPointsReq.getUserId();//先按积分降序,积分相同时按照最后更新时间升序,score = 积分 + (1 - 时间戳/10的13次方)double score = userPointsReq.getPoints() + (1 - userPointsReq.getUpdateTime() / 1e13);this.stringRedisTemplate.opsForZSet().add(redis_key, userId, score);}return true;}/*** 获取用户积分排行榜(倒序)** @param topN 前多少名* @return 前topN的用户积分排名列表*/@GetMapping("/userRankings")public List<UserRanking> userRankings(@RequestParam("topN") int topN) {// 从Redis中获取倒序排列的用户积分数据Set<ZSetOperations.TypedTuple<String>> typedTuples = this.stringRedisTemplate.opsForZSet().reverseRangeWithScores(redis_key, 0, topN - 1);List<UserRanking> userRankingList = new ArrayList<>();for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {UserRanking userRanking = new UserRanking();userRanking.setUserId(typedTuple.getValue());userRanking.setRedisScore(typedTuple.getScore());userRankingList.add(userRanking);}return userRankingList;}}

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

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

相关文章

栈与队列面试题(Java数据结构)

前言&#xff1a; 这里举两个典型的例子&#xff0c;实际上该类型的面试题是不确定的&#xff01; 用栈实现队列&#xff1a; 232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; 方法一&#xff1a;双栈 思路 将一个栈当作输入栈&#xff0c;用于压入 push 传入的数…

淘宝/天猫平台淘宝商品评论 API 技术文档分享

一、获得淘宝商品评论 API 的步骤与配置 注册淘宝开放平台账号&#xff1a; 访问淘宝开放平台 官网&#xff0c;注册并登录账号&#xff08;私信博主获得测试详情&#xff09;。完成企业或个人实名认证&#xff0c;确保账号具备开发应用的资质。创建应用&#xff1a; 在淘宝开…

初始爬虫12(反爬与反反爬)

学到这里&#xff0c;已经可以开始实战项目了&#xff0c;多去爬虫&#xff0c;了解熟悉反爬&#xff0c;然后自己总结出一套方法怎么做。 1.服务器反爬的原因 服务器反爬的原因 总结&#xff1a; 1.爬虫占总PV较高&#xff0c;浪费资源 2.资源被批量抓走&#xff0c;丧失竞争力…

动态规划10:174. 地下城游戏

动态规划解题步骤&#xff1a; 1.确定状态表示&#xff1a;dp[i]是什么 2.确定状态转移方程&#xff1a;dp[i]等于什么 3.初始化&#xff1a;确保状态转移方程不越界 4.确定填表顺序&#xff1a;根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接&#xff1a;174.…

小米路由器ax1500+DDNS+公网IP+花生壳实现远程访问

有远程办公的需求&#xff0c;以及一些其他东西。 为什么写&#xff1f; ax1500路由器好像没搜到相关信息。以及其中有一点坑。 前置 公网ip Xiaomi路由器 AX1500 MiWiFi 稳定版 1.0.54 实现流程 花生壳申请壳域名https://console.hsk.oray.com/ 这里需要为域名实名认证 …

Linux:进程调度算法和进程地址空间

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 进程调度算法 1.1 进程队列数据结构 1.2 优先级 ​编辑 1.3 活动队列 ​编辑 1.4 过期队列 1.5 active指针和expired指针 1.6 进程连接 二 进程地址空间 2.1 …

《大规模语言模型从理论到实践》第一轮学习--Fine-tuning微调

第一轮学习目标&#xff1a;了解大模型理论体系 第二轮学习目标&#xff1a;进行具体实操进一步深入理解大模型 从大语言模型的训练过程来理解微调 大预言模型训练主要包含四个阶段&#xff1a;预训练、有监督微调、奖励建模、强化学习。 预训练&#xff08;Pretraining&…

linux中缓存,在kafka上应用总结

linux中的缓存 页缓存 pagecatch&#xff08;读缓存用于提供快速读&#xff09;块缓存&#xff08;用于提供其他设备快速写&#xff09;当对读缓存读的时候&#xff0c;修改了读的数据&#xff0c;页缓存就会被标记为脏数据&#xff0c;等到写的时候它会向块缓存同步数据&…

Redis缓存穿透雪崩击穿及解决

封装缓存空对象解决缓存穿透与逻辑过期解决缓存击穿工具类 Slf4j Component public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplat…

PostgreSQL 字段使用pglz压缩测试

PostgreSQL 字段使用pglz压缩测试 测试一&#xff1a; 创建测试表 yewu1.test1&#xff0c;并插入1000w行数据 创建测试表 yewu1.test2&#xff0c;使用 pglz压缩字段&#xff0c;并插入1000w行数据–创建测试表1&#xff0c;并插入1000w行数据 white# create table yewu1.t…

Word办公自动化的一些方法

1.Word部分内容介绍 word本身是带有格式的一种文档&#xff0c;有人说它本质是XML&#xff0c;所以一定要充分利用标记了【样式】的特性来迅速调整【格式】&#xff0c;从而专心编辑文档内容本身。 样式&#xff08;集&#xff09; 编号&#xff08;多级关联样式编号&#xff…

操作系统 | 学习笔记 | 王道 | 3.1 内存管理概念

3 内存管理 3.1 内存管理概念 3.1.1 内存管理的基本原理和要求 内存可以存放数据&#xff0c;程序执行前需要先放到内存中才能被CPU处理—缓和cpu和磁盘之间的速度矛盾 内存管理的概念 虽然计算机技术飞速发展&#xff0c;内存容量也在不断扩大&#xff0c;但仍然不可能将所有…

Kubernetes-环境篇-02-ubuntu开发环境搭建

1、ubuntu基础环境 # 更新apt软件源 sudo apt update# 安装git sudo apt install git# 安装python3 sudo apt install -y python3 python3-pip# 安装vim sudo apt install vim2、安装go 2.1 下载go安装包 wget https://golang.google.cn/dl/go1.23.2.linux-amd64.tar.gz2.2 …

【Qt】控件概述(7)—— 布局管理器

布局管理器 1. 布局管理器2. QVBoxLayout——垂直布局3. QHBoxLayout——水平布局4. QGridLayout——网格布局5. QFormLayout——表单布局6. QSpacer 1. 布局管理器 在我们之前值ui界面进行拖拽设置控件时&#xff0c;都是通过手动的控制控件的位置的。同时每个控件的位置都是…

OpenGL ES 纹理(7)

OpenGL ES 纹理(7) 简述 通过前面几章的学习&#xff0c;我们已经可以绘制渲染我们想要的逻辑图形了&#xff0c;但是如果我们想要渲染一张本地图片&#xff0c;这就需要纹理了。 纹理其实是一个可以用于采样的数据集&#xff0c;比较典型的就是图片了&#xff0c;我们知道我…

《C++编程秘籍:实现高效加密数字签名算法》

在当今数字化时代&#xff0c;信息安全至关重要。加密数字签名算法作为保障数据完整性和真实性的重要手段&#xff0c;在 C编程中有着广泛的应用需求。本文将探讨如何在 C中实现高效的加密数字签名算法&#xff0c;为开发者提供实用的指南和思路。 一、加密数字签名算法的重要…

【STM32开发之寄存器版】(六)-通用定时器中断

一、前言 STM32定时器分类 STM32103ZET6具备8个定时器TIMx(x 1,2,...,8)。其中&#xff0c;TIM1和TIM8为高级定时器&#xff0c;TIM2-TIM6为通用定时器&#xff0c;TIM6和TIM7为基本定时器&#xff0c;本文将以TIM3通用定时器为例&#xff0c;分析STM32定时器工作的底层寄存器…

深度学习基础—残差网络ResNets

1.残差网络结构 当网络训练的很深很深的时候&#xff0c;效果是否会很好&#xff1f;在这篇论文中&#xff0c;作者给出了答案&#xff1a;Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…

EmEditor传奇脚本编辑器

主程序&#xff1a;EmEditor.exe 目前已有功能 可以自己指定一个快捷键 实现以下功能&#xff08;默认快捷键为&#xff1a;F1&#xff09; 以下全功能 都是鼠标所在行 按快捷键 &#xff08;默认快捷键&#xff1a;F1&#xff09; 1.在Merchant.txt中 一键打开NPC 没有…

PHP中for 和 foreach 有什么区别

在深入探讨PHP中的for和foreach循环的区别之前&#xff0c;我们先简要回顾一下这两种循环的基本概念和用途。随后&#xff0c;我们将从语法、用途、性能、可读性以及实际应用等多个方面&#xff0c;详细阐述它们之间的差异和各自的优势。 一、引言 在PHP中&#xff0c;for和f…