当前位置: 首页 > 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   欢迎大家与我交流! 


http://www.mrgr.cn/news/79652.html

相关文章:

  • vuex创建空仓库,store访问,mutations修改数据
  • Linux 简单命令总结
  • 使用OpenTK展示3D点云图像(C#)
  • (仓颉) Cangjie 刷力扣基础语法小结
  • Synchronizad优化原理(JUC)
  • 拿到小米 Offer,却迷茫了。。
  • 刷蓝桥杯历年考题(更新至15届~)
  • Python实现中国象棋
  • java全栈day12-后端Web实战(IOC+DI)
  • LNMP和Discuz论坛
  • uniapp 封装自定义头部导航栏
  • 北京大学《操作系统原理》(陈向群主讲)课堂笔记(一)
  • 【OpenCV】平滑图像
  • PHP:将数据传递给Grid++Report模板进行打印
  • jenkins邮件的配置详解
  • 【sgUploadImage】自定义组件:基于elementUI的el-upload封装的上传图片、相片组件,适用于上传缩略图、文章封面
  • HarmonyOS-高级(一)
  • GS-SLAM论文阅读--RGBDS-SLAM
  • Next.js 实战 (二):搭建 Layouts 基础排版布局
  • 组件上传图片不回显问题
  • 前端 —— Git
  • MVC基础——市场管理系统(二)
  • PCB设计规范
  • Centos7和9安装mysql5.7和mysql8.0详细教程(超详细)
  • Qt C++ 显示多级结构体,包括结构体名、变量名和值
  • TEA系列例题