当前位置: 首页 > 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.lryc.cn/news/500772.html

相关文章:

  • elasticsearch基础总结
  • 【慕伏白教程】Zerotier 连接与简单配置
  • Brain.js(九):LSTMTimeStep 实战教程 - 未来短期内的股市指数预测 - 实操要谨慎
  • C# 字符串(String)
  • 二进制文件
  • 【电子元器件】音频功放种类
  • linux之vim
  • QT的ui界面显示不全问题(适应高分辨率屏幕)
  • 数据结构--串、数组和广义表
  • LLMs之Agent之Lares:Lares的简介、安装和使用方法、案例应用之详细攻略
  • 1-1.mysql2 之 mysql2 初识(mysql2 初识案例、初识案例挖掘)
  • 企业邮箱为什么不能经常群发邮件?
  • 集成运算放大电路反馈判断
  • 媒体查询、浏览器一帧渲染过程
  • 高级排序算法(一):快速排序详解
  • 3.2 网络协议IP
  • 2024 一带一路暨金砖国家技能发展与技术创新大赛【网络安全防护治理实战技能赛项】样题(中职组)
  • excel如何让单元格选中时显示提示信息?
  • oscp备考,oscp系列——Kioptix Level 3靶场
  • 信创改造-达梦数据库配置项 dm.ini 优化
  • 日本IT-需要掌握哪些技术框架?一篇通读
  • 错题:Linux C语言
  • 多表设计-一对多一对多-外键
  • Ch1:古今的manipulation与仿真、ROS和Drake介绍
  • JAVA秋招面试题精选-第一天总结
  • 服务器卸载安装的 Node.js
  • 深度解析 Ansible:核心组件、配置、Playbook 全流程与 YAML 奥秘(下)
  • 使用go生成、识别二维码
  • LLama系列模型简要概述
  • 2022 年“泰迪杯”数据分析技能赛A 题竞赛作品的自动评判