当前位置: 首页 > 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…...

信创改造-达梦数据库配置项 dm.ini 优化

设置模式&#xff1a;兼容MySQL&#xff0c;COMPATIBLE_MODE 4 内存占比&#xff1a;90%&#xff0c;MAX_OS_MEMORY 90 目标内存&#xff1a;2G&#xff08;不影响申请内存超过2G&#xff0c;但这部分内存不会回收&#xff09;&#xff0c;MEMORY_TARGET 2000 参考 https:…...

日本IT-需要掌握哪些技术框架?一篇通读

在日本从事IT工作&#xff0c;需要掌握的技术框架与全球范围内的趋势相似&#xff0c;但也有一些特定的技术和框架在日本更为流行。以下是一些在日本IT行业中常用的技术框架&#xff1a; Java后端 Java语言&#xff1a;Java在日本是一门非常稳定且受欢迎的编程语言&#xff0…...

错题:Linux C语言

题目&#xff1a;手写代码&#xff1a;判断一个数&#xff08;int类型的整数&#xff09;中有有多少1 题目&#xff1a;手写代码&#xff1a;判断一个数(转换成二进制表示时)有几个1 #include <stdio.h> int main(int argc, const char *argv[]) { //判断一个数&#xf…...

多表设计-一对多一对多-外键

一.多表设计概述&#xff1a; 二.一对多&#xff1a; 1.需求&#xff1a; 根据 页面原型 及 需求文档&#xff0c;完成部门及员工模块的表结构设计 -->部门和员工就是一对多&#xff0c;因为一个部门下会有多个员工&#xff0c;但一个员工只归属一个部门 2.页面原型&…...

Ch1:古今的manipulation与仿真、ROS和Drake介绍

不同的机器人研究与仿真 以前&#xff08;15年左右&#xff09;只能用仿真环境训练行走机器人&#xff0c;对于manipulation任务&#xff0c;有两个问题&#xff1a;1&#xff09;相机不真实&#xff1b;2&#xff09;接触行为太复杂。 I remember just a few years ago (~201…...

JAVA秋招面试题精选-第一天总结

目录 分栏简介&#xff1a; 问题一&#xff1a;订单表每天新增500W条数据&#xff0c;分库分表应该怎么设计&#xff1f; 问题难度以及频率&#xff1a; 问题导向&#xff1a; 满分答案&#xff1a; 举一反三&#xff1a; 问题总结&#xff1a; 问题二&#xff1a;解释…...

服务器卸载安装的 Node.js

卸载安装的 Node.js 版本&#xff0c;具体步骤取决于你是通过包管理器&#xff08;如 yum 或 dnf&#xff09;安装的&#xff0c;还是通过 nvm (Node Version Manager) 安装的。以下是针对这两种情况的指南。 通过包管理器卸载 Node.js 如果你是通过 yum 或 dnf 安装的 Node.…...

深度解析 Ansible:核心组件、配置、Playbook 全流程与 YAML 奥秘(下)

文章目录 六、playbook运行playbook方式Playbook VS ShellScripts忽略错误 ignore_errorshandlers和notify结合使用触发条件playbook中tags的使用playbook中变量的使用invertory参数模板templates迭代与条件判断迭代&#xff1a;with_items迭代嵌套子变量roles 六、playbook 运…...

使用go生成、识别二维码

1、下载 # 创建目录 # 进入目录 # 执行 go mod init xxx 命令&#xff08;即&#xff1a;在当前目录初始化创建一个模块&#xff09;# 下载gozxing go get github.com/makiuchi-d/gozxing 2、生成二维码 package mainimport ("image/png""os""gith…...

LLama系列模型简要概述

LLama-1&#xff08;7B, 13B, 33B, 65B参数量&#xff1b;1.4T tokens训练数据量&#xff09; 要做真正Open的AI Efficient&#xff1a;同等预算下&#xff0c;增大训练数据&#xff0c;比增大模型参数量&#xff0c;效果要更好 训练数据&#xff1a; 书、Wiki这种量少、质量高…...