当前位置: 首页 > news >正文

【系统架构核心服务设计】使用 Redis ZSET 实现排行榜服务

目录

一、排行榜的应用场景

二、排行榜技术的特点

三、使用Redis ZSET实现排行榜

3.1 引入依赖

3.2 配置Redis连接

3.3 创建实体类(可选)

3.4 编写 Redis 操作服务层 

3.5 编写控制器层

3.6 测试

3.6.1 测试 addMovieScore 接口

3.6.2 测试 getTopNRankings 接口 

3.6.3 测试 getMovieRank接口 

3.6.4 测试 getMovieScore接口 

前端测试代码


一、排行榜的应用场景

排行榜服务是一个看似简单但又复杂的设计,其在互联网产品中应用非常广泛:

  • 游戏排行榜
  • 商品排行榜
  • 视频排行榜
  • 社交排行榜

互联网应用提供排行榜功能可以对关键信息起到增强曝光的作用,并且可以在一定程度上提供用户的活跃度、参与度,从而促进互联网产品的发展。

二、排行榜技术的特点

与现实生活中的排行榜不同,互联网应用中的排行榜一般具有如下特点。

  • 曝光量大
  • 竞争激烈
  • 实时变化
  • 周期滚动

所以,在排行榜的技术实现方面,要重点考虑高并发读/写、实时展示最新排名,以及可以轻松支持周期滚动的能力

在设计排行榜服务时,首先要考虑的问题是使用什么存储系统来维护排行榜。假如使用关系型数据库的话,因为它对高并发读/写的支持较弱面且为了支持按照评分排序,在关系型数据库中需要根据分数/积分字段,使用SELECT语句的ORDER BY子句来实现。而该方式具有如下缺点

  • 性能开销:在有大量数据的情况下,排序操作会耗费大量的系统资源和处理时间,尤其是当需要进行多字段排序或者排序字段的数据类型不同时,查询效率更低。
  • 磁盘I/O:当需要对大量数据进行排序时,可能要使用临时表或者磁盘存储技术,使排序操作不再全部运行在内存中,而这需要进行大量的磁盘读/写操作,从面导致性能降低,查询的响应时间变长。

所以,实现排行榜不太适合使用关系型数据库。排行榜是按照积分排序的,因此很容易让人想到Redis的ZSET数据结构。ZSET是一种有序集合形式,该集合由Member组成,每个Member都有一个Score(积分),集合会按照Score自动排序。所以,目前Redis ZSET便成为实现排行榜的首选。

补充:ZSET底层数据结构是通过压缩列表和跳表实现的:

 

三、使用Redis ZSET实现排行榜

3.1 引入依赖

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.7.0</version> <!-- 使用合适的版本 -->
</dependency>

关于 Redis 客户端库 — Jedis :

        在 Java 项目中,如果需要通过代码去连接 Redis 数据库、执行如设置键值对、获取数据、进行列表操作、发布订阅等各种 Redis 支持的操作时,就需要引入 Jedis 库。

        这段依赖配置就是为了方便在 Java 项目中引入 Jedis 这个强大的 Redis 客户端库,从而能在代码层面和 Redis 数据库进行交互操作,实现各种基于 Redis 存储和缓存等功能需求。

3.2 配置Redis连接

在 application.yml文件中配置 Redis 连接信息:

  redis:host: localhost  # 修改为实际Redis主机地址port: 6379  # 修改为实际Redis端口password: 123  # 修改为实际Redis密码database: 0  # 选择使用的数据库,默认为0

3.3 创建实体类(可选)

这一步根据业务需求选择,如果需要存储更复杂的电影相关信息用于排行榜,可以创建对应的实体类,这里简单以电影 ID 和评分为例

@Data
@Component
@AllArgsConstructor
@NoArgsConstructor
public class MovieScore {private String movieId;private double rating;
}

关于Lombok的安装和使用请参考:(在文章末尾)

Spring框架学习 有这一篇就够!_spring 学习-CSDN博客

3.4 编写 Redis 操作服务层 

package com.snut.selltickets.service;import com.snut.selltickets.model.MovieScore;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Set;
import java.util.stream.Collectors;@Service
public class RankService {private static final  String RANKING_KEY = "movie_ranking";@Resourceprivate RedisTemplate<String, String> redisTemplate;// 添加电影评分到排行榜public void addMovieScore(String movieId, double rating) {ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();zSetOperations.add(RANKING_KEY, movieId, rating);}// 获取排行榜前N名的电影(这里返回电影ID和对应评分)public Set<MovieScore> getTopNRankings(int n) {ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();return zSetOperations.reverseRangeWithScores(RANKING_KEY, 0, n - 1).stream().map(tuple -> new MovieScore(tuple.getValue(), tuple.getScore())).collect(Collectors.toSet());}// 获取电影在排行榜中的排名(从高到低排序,排名从0开始)public Long getMovieRank(String movieId) {ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();return zSetOperations.reverseRank(RANKING_KEY, movieId);}// 获取电影的评分public Double getMovieScore(String movieId) {ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();return zSetOperations.score(RANKING_KEY, movieId);}}

3.5 编写控制器层

用于对外提供接口,可测试调用

import com.snut.selltickets.model.MovieScore;
import com.snut.selltickets.model.Result;
import com.snut.selltickets.service.RankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.Set;@RestController
@RequestMapping("/userApi/RankCtl")
public class RankCtl {@AutowiredRankService rankService;// 添加电影评分接口@PostMapping("/add")public ResponseEntity<String> addMovieScore(@RequestBody MovieScore movieScore) {rankService.addMovieScore(movieScore.getMovieId(),movieScore.getRating());return ResponseEntity.ok("电影评分成功");}// 获取排行榜前N名接口@GetMapping("/topN")public ResponseEntity<Set<MovieScore>> getTopNRankings(@RequestParam("n") int n) {Set<MovieScore> topNRankings = rankService.getTopNRankings(n);return ResponseEntity.ok(topNRankings);}// 获取电影排名接口@GetMapping("/movieRank")public ResponseEntity<Long> getMovieRank(@RequestParam("movieId") String movieId) {Long userRank = rankService.getMovieRank(movieId);return ResponseEntity.ok(userRank);}// 获取电影积分接口@GetMapping("/movieRating")public ResponseEntity<Double> getMovieScore(@RequestParam("movieId") String movieId) {Double userScore = rankService.getMovieScore(movieId);return ResponseEntity.ok(userScore);}}

3.6 测试

我这里是在前端简单模拟了一个测试器:

3.6.1 测试 addMovieScore 接口

 

3.6.2 测试 getTopNRankings 接口 

这里返回排行榜前topN(3)的电影信息 

3.6.3 测试 getMovieRank接口 

3.6.4 测试 getMovieScore接口 

前端测试代码

<template><div id="app"><div style="height: 120px;"></div>movieId:<input type="text" v-model="form.movieId"/>rating:<input type="text" v-model="form.rating"/>topN:<input type="text" v-model="n"/><div style="margin-bottom: 30px;"></div><button style="width: 150px; height: 100px;" @click="add()">add</button><button style="width: 150px; height: 100px;" @click="topN()">topN</button><button style="width: 150px; height: 100px;" @click="getMovieRank()">getMovieRank</button><button style="width: 150px; height: 100px;" @click="getMovieScore()">getMovieScore</button></div>
</template><script>export default {data() {return {form:{movieId:"",rating:""},n:""}},methods: {add() {this.$http.post("userApi/RankCtl/add",this.form).then((resp) => {this.$message({message: resp.data,type: 'success'});this.$router.go(0); //更新当前的路由 组件});},topN() {this.$http.get("userApi/RankCtl/topN?n="+this.n).then((resp) => {this.$message({message: resp.data,type: 'success'});console.log(resp.data);});},getMovieRank() {this.$http.get("userApi/RankCtl/movieRank?movieId="+this.form.movieId).then((resp) => {this.$message({message: resp.data,type: 'success'});console.log(resp.data);});},getMovieScore() {this.$http.get("userApi/RankCtl/movieRating?movieId="+this.form.movieId).then((resp) => {this.$message({message: resp.data,type: 'success'});console.log(resp.data);});}},mounted() {}}
</script><style scoped>#app{width: 150px;margin: 0 auto;}
</style>


🌸🌸🌸 完结撒花 🌸🌸🌸

  博主WX:g2279605572   欢迎大家与我交流! 

相关文章:

【系统架构核心服务设计】使用 Redis ZSET 实现排行榜服务

目录 一、排行榜的应用场景 二、排行榜技术的特点 三、使用Redis ZSET实现排行榜 3.1 引入依赖 3.2 配置Redis连接 3.3 创建实体类&#xff08;可选&#xff09; 3.4 编写 Redis 操作服务层 3.5 编写控制器层 3.6 测试 3.6.1 测试 addMovieScore 接口 3.6.2 测试 g…...

elasticsearch基础总结

最近实习&#xff0c;项目用的elasticseatch做的存储库&#xff0c;但是之前对于es接触的不多&#xff0c;查询语法有些不熟&#xff0c;每次想写个DSL查询时都要gpt或者施展搜索大法&#xff0c;所以索性就自己总结总结&#xff0c;以后忘了也方便查。所以这篇文章会持续更新。…...

【慕伏白教程】Zerotier 连接与简单配置

文章目录 下载与安装WindowsLinuxapt安装官方脚本安装 Zerotier 配置新建网络网络配置 终端配置WindowsLinux 下载与安装 Windows 进入Zerotier官方下载网站&#xff0c;点击下载 在下载目录找到安装文件&#xff0c;双击打开后点击 Install 开始安装 安装完成后&#xff0c;…...

Brain.js(九):LSTMTimeStep 实战教程 - 未来短期内的股市指数预测 - 实操要谨慎

系列的前一文RNNTimeStep 实战教程 - 股票价格预测 讲述了如何使用RNN时间序列预测实时的股价&#xff0c; 在这一节中&#xff0c;我们将深入学习如何利用 JavaScript 在浏览器环境下使用 LSTMTimeStep 进行股市指数的短期预测。通过本次实战教程&#xff0c;你将了解到如何用…...

C# 字符串(String)

文章目录 前言创建 String 对象的方式1. 通过给 String 变量指定一个字符串2. 通过使用 String 类构造函数3. 通过使用字符串串联运算符&#xff08; &#xff09;4. 通过检索属性或调用一个返回字符串的方法5. 通过格式化方法来转换一个值或对象为它的字符串表示形式 String …...

二进制文件

大多数人听到“二进制”的时候&#xff0c;脑海里可能马上就会联想到电影《黑客帝国》中由“0”和“1”组成的矩阵。 笔者不打算在这里详细讨论二进制的运算、反码、补码之类枯燥的东西&#xff0c;但有几个和开发相关的概念需要做一点澄清和普及。因为这些内容就像空气——用…...

【电子元器件】音频功放种类

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时&#xff0c;也能帮助其他需要参考的朋友。如有谬误&#xff0c;欢迎大家进行指正。 一、概述 音频功放将小信号的幅值提高至有用电平&#xff0c;同时保留小信号的细节&#xff0c;这称为线性度。放大器的线性…...

linux之vim

一、模式转换命令 vim主要有三种模式&#xff1a;命令模式&#xff08;Normal Mode&#xff09;、输入模式&#xff08;Insert Mode&#xff09;和底线命令模式&#xff08;Command-Line Mode&#xff09;。 从命令模式切换到输入模式&#xff1a;i&#xff1a;在当前光标所在…...

QT的ui界面显示不全问题(适应高分辨率屏幕)

//自动适应高分辨率 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);一、问题 电脑分辨率高&#xff0c;默认情况下&#xff0c;打开QT的ui界面&#xff0c;显示不全按钮内容 二、解决方案 如果自己的电脑分辨率较高&#xff0c;可以尝试以下方案&#xff1a;自…...

数据结构--串、数组和广义表

串 定义&#xff1a;串&#xff08;String&#xff09;是由零个或多个字符组成的有限序列。 子串&#xff1a;串中任意个连续字符组成的子序列称为该串的子串。 主串&#xff1a;包含子串的串相应地称为主串。 字符位置&#xff1a;字符在该序列中的序号为该字符在串中的位置…...

LLMs之Agent之Lares:Lares的简介、安装和使用方法、案例应用之详细攻略

LLMs之Agent之Lares&#xff1a;Lares的简介、安装和使用方法、案例应用之详细攻略 导读&#xff1a;这篇博文介绍了 Lares&#xff0c;一个由简单的 AI 代理驱动的智能家居助手模拟器&#xff0c;它展现出令人惊讶的解决问题能力。 >> 背景痛点&#xff1a;每天都有新的…...

1-1.mysql2 之 mysql2 初识(mysql2 初识案例、初识案例挖掘)

一、mysql2 概述 mysql2 是一个用于 Node.js 的 MySQL 客户端库 mysql2 是 mysql 库的一个改进版本&#xff0c;提供了更好的性能和更多的功能 使用 mysql2 之前&#xff0c;需要先安装它 npm install mysql2 二、mysql2 初识案例 1、数据库准备 创建数据库 testdb CREAT…...

企业邮箱为什么不能经常群发邮件?

企业邮箱是用企业域名作为后缀的邮箱&#xff0c;虽然企业邮箱确实具备群发邮件的功能&#xff0c;但它更适用于企业内部的群发&#xff0c;而非用于外部推广。如果是在企业邮件域内进行群发&#xff0c;通常可以借助企业邮箱的邮件列表来实现。然而&#xff0c;对于域外的大量…...

集成运算放大电路反馈判断

集成运算放大电路 一种具有很高放大倍数的多级直接耦合放大电路&#xff0c;因最初用于信号运算而得名&#xff0c;简称集成运放或运放 模拟集成电路中的典型组件&#xff0c;是发展最快、品种最多、应用最广的一种 反馈 将放大电路输出信号的一部分或全部通过某种电路引回到输…...

媒体查询、浏览器一帧渲染过程

文章目录 媒体查询语法示例根据视口宽度应用不同的样式根据设备像素比应用不同的样式根据方向应用不同的样式 使用场景 浏览器一帧的渲染过程 媒体查询 媒体查询&#xff08;Media Query&#xff09;是CSS3中的一个重要特性&#xff0c;它允许开发者根据设备的特定条件&#x…...

高级排序算法(一):快速排序详解

引言 当我们处理大规模数据时&#xff0c;像冒泡排序、选择排序这样的基础排序算法就有点力不从心了。这时候&#xff0c;快速排序&#xff08;Quick Sort&#xff09;就派上用场了。 作为一种基于分治法的高效排序算法&#xff0c;快速排序在大多数情况下可以在O(n log n)的时…...

3.2 网络协议IP

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 1 定义2 虚拟互连网络3 分组在互联网中的传送4 IPv4 地址 1 定义 网际协议 IP是 TCP/IP 体系中两个最主要的协议之一&#xff0c;也是最重要的互连网协议之一。IPv4 和 IPv6 …...

2024 一带一路暨金砖国家技能发展与技术创新大赛【网络安全防护治理实战技能赛项】样题(中职组)

2024 一带一路暨金砖国家技能发展与技术创新大赛【网络安全防护治理实战技能赛项】样题&#xff08;中职组&#xff09; 1.基础设置和安全强化&#xff08;xxx 分&#xff09;1.3. 任务内容: 2.安全监测和预警&#xff08;xxx 分&#xff09;2.1. 任务一&#xff1a;建立目录安…...

excel如何让单元格选中时显示提示信息?

现象&#xff1a; 当鼠标放在单元格上&#xff0c;会出现提示信息&#xff1a; 先选中单元格选择上方的【数据】-【数据验证】图标选择【输入信息】勾上【选定单元格时显示输入信息】输入【标题】&#xff0c;如&#xff1a;最上方图中的&#xff1a;姓名&#xff1a;输入【输…...

oscp备考,oscp系列——Kioptix Level 3靶场

Kioptix Level 3 oscp备考&#xff0c;oscp系列——Kioptix Level 3靶场 nmap扫描 主机发现 └─# nmap -sn 192.168.80.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-09 00:33 CST Nmap scan report for 192.168.80.1 Host is up (0.00014s latency). MAC…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献

Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译&#xff1a; ### 胃肠道癌症的发病率呈上升趋势&#xff0c;且有年轻化倾向&#xff08;Bray等人&#xff0c;2018&#x…...

【Qt】控件 QWidget

控件 QWidget 一. 控件概述二. QWidget 的核心属性可用状态&#xff1a;enabled几何&#xff1a;geometrywindows frame 窗口框架的影响 窗口标题&#xff1a;windowTitle窗口图标&#xff1a;windowIconqrc 机制 窗口不透明度&#xff1a;windowOpacity光标&#xff1a;cursor…...

02-性能方案设计

需求分析与测试设计 根据具体的性能测试需求&#xff0c;确定测试类型&#xff0c;以及压测的模块(web/mysql/redis/系统整体)前期要与相关人员充分沟通&#xff0c;初步确定压测方案及具体的性能指标QA完成性能测试设计后&#xff0c;需产出测试方案文档发送邮件到项目组&…...

学习 Hooks【Plan - June - Week 2】

一、React API React 提供了丰富的核心 API&#xff0c;用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。 1. React 核心 API React.createElement(type, props, …children) 用于创建 React 元素&#xff0c;JSX 会被编译成该函数…...