【系统架构核心服务设计】使用 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   欢迎大家与我交流! 

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

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

相关文章

架构15-服务网格

零、文章目录 架构15-服务网格 1、透明通信的涅槃 &#xff08;1&#xff09;服务网格 概念 服务网格是一种处理程序间通信的基础设施&#xff0c;主要由数据平面和控制平面组成。它通过边车代理和控制程序管理程序间的通信&#xff0c;弥补了容器编排系统对分布式应用细粒…

constexpr、const和 #define 的比较

constexpr、const 和 #define 的比较 一、定义常量 constexpr 定义&#xff1a;constexpr用于定义在编译期可求值的常量表达式。示例&#xff1a;constexpr int x 5;这里&#xff0c;x的值在编译期就确定为5。 const 定义&#xff1a;const表示变量在运行期间不能被修改&…

C# RSA加密和解密,RSA生成私钥和公钥

C# RSA加密和解密&#xff0c;RSA生成私钥和公钥&#xff08;使用XML格式秘钥&#xff09; 目录 前言生成xml格式的公钥和私钥 PrivateKeyPublicKey测试加密、解密 方案1&#xff1a;RSA公钥加密&#xff0c;RSA私钥解密方案2&#xff1a;RSA私钥加密&#xff0c;RSA私钥解密…

洛谷P1208

[USACO1.3] 混合牛奶 Mixing Milk - 洛谷 [USACO1.3] 混合牛奶 Mixing Milk 题目描述 由于乳制品产业利润很低&#xff0c;所以降低原材料&#xff08;牛奶&#xff09;价格就变得十分重要。帮助 Marry 乳业找到最优的牛奶采购方案。 Marry 乳业从一些奶农手中采购牛奶&…

Linux命令进阶·软链接命令(ln)、查看系统时间命令(date)、自动校准系统时间程序(ntp)

目录 1. 软链接——ln命令 2. 查看系统时间——date命令 3. 自动校准系统时间——ntp程序 1. 软链接——ln命令 在系统中创建软链接&#xff0c;可以将文件、文件夹链接到其他位置。作用相当于windows中的快捷方式。 语法&#xff1a;ln -s 参数1 参数2 -s选项&#xff…

RabbitMQ如何保证消息不被重复消费

前言&#xff1a; 正常情况下&#xff0c;消费者在消费消息后&#xff0c;会给消息队列发送一个确认&#xff0c;消息队列接收后就知道消息已经被成功消费了&#xff0c;然后就从队列中删除该消息&#xff0c;也就不会将该消息再发送给其他消费者了。不同消息队列发出的确认消…

#Vue3篇:生命周期简洁

setup类似breforeCreate create setup() 钩子是在组件中使用组合式 API 的入口 挂载 onBeforeMount组件被挂载之前执行 onMounted 组件挂载完后执行 更新 onBeforeUpdate组件响应式状态变更而更新Dom树之后执行 onUpdated 组件响应式状态变更而更新Dom树之后执行 卸载 …

java 使用JSqlParser和CCJSqlParser 解析sql

maven <dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.9</version> </dependency>解析SQL String sql "select aa,bb from b"; Statement statementCCJSq…

动态规划——机器分配、01背包问题

一、机器分配 题目名称&#xff1a;机器分配 题目描述&#xff1a; 总公司拥有高效设备M台&#xff0c;准备分给下属的N个分公司。 各分公司若获得这些设备&#xff0c;可以为国家提供一定的盈利。 问&#xff1a;如何分配这M台设备才能使国家得到的盈利最大&#xff1f;求出最…

深入解析 JavaScript 中的 Blob 对象:二进制数据处理的核心

文章目录 1.Blob是什么2.Blob用法实例属性Blob方法slice方法text方法 示例1&#xff1a;字符串 Blob示例2&#xff1a;数组和字符串 Blob示例3&#xff1a;从文件输入创建 3.使用场景1.创建 Blob 并生成 URL&#xff0c;下载文件2.文件上传3.切片上传3.Blob用于URL在线预览PDF文…

NanoLog起步笔记-7-log解压过程初探

nonolog起步笔记-6-log解压过程初探 再看解压过程建立调试工程修改makefile添加新的launch项 注&#xff1a;重新学习nanolog的README.mdPost-Execution Log Decompressor 下面我们尝试了解&#xff0c;解压的过程&#xff0c;是如何得到文件头部的meta信息的。 再看解压过程 …

人工智能大模型LLM开源资源汇总(持续更新)

说明 目前是大范围整理阶段&#xff0c;所以存在大量机翻说明&#xff0c;后续会逐渐补充和完善资料&#xff0c;减少机翻并增加说明。 Github上的汇总资源&#xff08;大部分英文&#xff09; awesome-production-machine-learning 此存储库包含一系列精选的优秀开源库&am…

C++实现排序算法:冒泡排序

目录 前言 冒泡排序性质 C代码实现冒泡排序 冒泡图解 第一趟排序 第二趟排序 第三趟排序 排序结果 结语 前言 冒泡排序的基本思想是通过从前往后&#xff08;从后往前&#xff09;两两比较&#xff0c;若为逆序&#xff08;即arr[i] < arr[i 1]&#xff09;则交换…

中介者模式的理解和实践

一、中介者模式概述 中介者模式&#xff08;Mediator Pattern&#xff09;&#xff0c;也称为调解者模式或调停者模式&#xff0c;是一种行为设计模式。它的核心思想是通过引入一个中介者对象来封装一系列对象之间的交互&#xff0c;使得这些对象不必直接相互作用&#xff0c;从…

PlantUML——类图

背景 类图是UML模型中的静态视图&#xff0c;其主要作用包括&#xff1a; 描述系统的结构化设计&#xff0c;显示出类、接口以及它们之间的静态结构和关系。简化对系统的理解&#xff0c;是系统分析与设计阶段的重要产物&#xff0c;也是系统编码和测试的重要模型依据。 在U…

【Java知识】Java进阶-如何开启远程调式

java远程调试 概述Java远程调试的开启与底层原理开启Java远程调试底层原理 JVM参数 概述 Java远程调试的开启与底层原理 开启Java远程调试 Java远程调试允许开发者在本地IDE&#xff08;如Eclipse、IntelliJ IDEA等&#xff09;中调试运行在远程服务器上的Java应用程序。以下…

入选国家数据局案例!浙江“一体化智能化公共数据平台”总体架构详解~

国家数据局在2024中国国际大数据产业博览会上发布了首批50个数字中国建设典型案例。案例涉及数据基础设施、数据资源、数字技术创新、数字政府、数字经济、数字社会、数字文化、数字生态文明等领域&#xff0c;集中反映了近年来数字中国建设的实践和成效。 其中&#xff0c;由…

vscode IntelliSense Configurations

IntelliSense 是一个强大的代码补全和代码分析功能&#xff0c;它可以帮助开发者提高编程效率。图中显示的是 VSCode 的 IntelliSense 配置界面&#xff0c;具体配置如下&#xff1a; Compiler path&#xff08;编译器路径&#xff09;: 这里指定了用于构建项目的编译器的完整路…

同三维TL200H2S6 6机位精品教育录播主机

录制点播、直播导播、互动、音频处理器、中控等多功能为一体 8路视频输入:6路SDI1路HDMI1(4K30)1路(3选1:HDMI2/2路VGA) 2路视频输出&#xff1a;1路HDMI1(4K30)1路&#…

欢乐斗地主案例

1&#xff0c;创建对象 package anlidoudizhu;public class pai implements Comparable<pai> {private Double value;private String name;Overridepublic int compareTo(pai o) {return -Double.compare(this.value,o.value);}//升序排序&#xff0c;treeset集合public…