基于Redis中 Zset数据类型实现各类高性能排行榜日榜、周榜(附上整合SpringBoot示例代码)

Redis实现用户活跃排行榜 在这里用户活跃度排行榜,主要是基于redis的zset数据结构来实现的,下面来看一下实例。 阅读详情

目录

为什么要使用redis?

日榜、周榜实现方案介绍

热度增加代码参考

日榜获取

周榜获取


为什么要使用redis?

如果使用mysql

要实现排行榜功能,如果使用 mysql或其它关系型数据库 来进行排行,我们大致的思路就是在这个表再定义出一个字段 如hot 来记录热度值,然后在数据库查询时通过 order by hot 来实现排行。如果有影响排行热度的操作就直接 update 修改数据库表。

这样做的优缺点也很明显,就拿 博客热度排行 而言,可能很多操作都会影响到热度的变化。如浏览、评论、点赞等等,这也容易对数据库造成压力。

  • 优点 就是代码操作简单,有事项就执行sql语句修改一下热度值。
  • 缺点 就是频繁的数据库操作会对服务器造成一定的压力,效率也比较低。

redis实现

Redis中一共有 5 种数据类型,其中 ZSet 是一个比较特殊的数据类型,

我们可以先了解 ZSet 的大体结构,

ZSet 数据的 value 有多列,每列的成员包含 member 和 score。

ZSet 的有关底层数据结构是 压缩列表、packlist、跳表。可以在去看看其他的博客了解详情。


对于实现 排行榜 来说,一般 member 存放对应 排行内容单项的 id 值,有相关的操作修改 score

score(分值)可以 来对存入数据进行一个排序。并通过

zincrby key increment member

来实现一个热度的 原子递增 来保证线程安全。

那么使用 redis 来实现这个功能的优势已经不言而喻了,感觉 ZSet 这个数据类型就是为排行而生的,同时 redis 作为一项高性能 nosql 也更高地提高了程序的并发性能,效率也高很多。

(听说王者荣耀的排行榜就是用 Redis 实现的)

拓展:redis为什么快,却又不能替代mysql?

虽然 Redis 和 mysql 都可以进行持久化,但是 Redis 的持久化主要是为了 保护数据,防止宕机等原因造成数据丢失。

在运行时,Redis 还是需要将所有的数据加载到内存当中。而 mysql 大部分数据是在 硬盘 当中的,当需要进行加载的时候,进行 IO 操作,将对应的页内容加载进内存操作。

当然 我们知道 mysql 的 innoDB 引擎是基于 B+ 树存储的。像 B+ 树 的 根节点数据内容一般也是常驻内存的。

这个问题我举一个关于 家里空间使用的例子 帮助大家简单理解下这个问题。

这里将 卧室 比作 内存, 客厅 比作 磁盘

  1. 类似于我们躺在房间里要拿 东西(数据),如果东西就在 房间 里(服务器内存里)
  2. 我们只要一伸手就能拿到。如果东西放在 客厅或者储物间 里(硬盘中),我们则需要走到外面去(效率低)才能拿到这项东西。
  3. 但是房间里的 空间有限(内存小),不能存放太多的物品。所以在东西多的时候,还是需要把大部分东西放在 客厅或者储物间(硬盘)。
  4. 同时如果房间里 东西多了(内存较满),也会影响我们的 通行(影响执行效率)。

另外,在内存的数据还可能因为服务器宕机等原因而丢失数据。

日榜、周榜实现方案介绍

既然是排行榜就要有区分排行榜 每一天 或 每一个周 的根据,我们通过操作 key 的后缀 来实现。

所以你的排行榜有多长的周期,可以通过后缀 来进行控制。

redis key生成策略

通过获取 时间戳,除以每日的单位 1000 * 60 * 60 * 24 得到一个 day key ,积累今日用户操作进行的分值累积
通过算法算出上周的 week key,对上周 day key 进行累积合并操作生成
得到的 day key 或 week key 将用来作为排行榜存入redis 的 key 后置参数

(假设daykey 为 5,那么就生成 key 为 rank_day:5),redis可视化工具中:将为我们自动分文件夹


创建 key 时,指定 TTL 为 30 天,(TTL 视业务而定,如月榜,那么数据就需要存储30天)避免垃圾数据占用内存
Redis 提供了 ZSet 的合并操作,我们可以用来实现周、月排行榜。如周榜就是 合并前 7 天的key 或者 上一周 7 天的 key

代码参考

热度增加代码参考

热度增加,在当日的 key上进行操作,如果接口请求日榜就返回当日信息,如果请求的是周榜就新型合并操作

这里进行是否创建判断主要是为了 设置 TTL,避免造成空间浪费。

	@Override
	public void addRankHotScore(Integer blogId, Double score) {
		// 获取dayKey
		long dayKey = RankKeyUtils.getDayKey();
		//封装 redis key
		String key = RANK_HOT_DAY_KEY + dayKey;
		//进行判断,是否已经创建 该redis
		if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
			//如果没有创建,就执行创建并设置 TTL 为40天
			redisTemplate.opsForZSet().incrementScore(key, blogId, score);
			redisTemplate.expire(key, RANK_HOT_DAY_TTL, TimeUnit.SECONDS);
		}
		//已创建,将对应博客热度增加 3
		redisTemplate.opsForZSet().incrementScore(key, blogId, score);
	}

日榜获取

日 key算法:

	/**
	 * 获取 day key
	 */
	public static long getDayKey() {
		return System.currentTimeMillis() / (1000 * 60 * 60 * 24);
	}

获取日榜信息

	@Override
	public List<RankHotVO> getTodayHotRank() {
		// 1 进行判断 redis 是否已经创建了 今天的redis 排行榜缓存的key
		// 1.1 获取 day key
		long dayKey = RankKeyUtils.getDayKey();
		// 获取redis数据
		Set<ZSetOperations.TypedTuple<Integer>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(RANK_HOT_DAY_KEY + dayKey, 0, -1);
		return getRankHotVOList(typedTuples);
	}

周榜获取

周 key算法

这里是获取上一周 7 天的算法,而不是 前7天

	/**
	 * 获取上一周的 week key
	 */
	public static long getWeekKey() {
		// 拿到 week key,和day key
		long week = System.currentTimeMillis() / (1000 * 60 * 60 * 24 * 7);
		long day = getDayKey();
		// 时间戳取模到周key,不是刚好从周一算起的,如果dayKey%7==4,这样这天才是刚好周一
		// 由于原来的week key并不是从周一开始算起,所以进行移位计算,拿到上周的week key
		long key = day % 7;
		if (key >= 4) {
			// 经计算,取模值大于等于 4 时 进入新的一周,将week key-1
			return week - 1;
		}
		//否则,在新的一周的周四时,week值将再加1,所以要拿到上一周的week key需要-2
		return week - 2;
	}

获取周榜信息

	@Override
	public List<RankHotVO> getWeekHotRank() {
		long weekKey = RankKeyUtils.getWeekKey();
		// 拿到数据集合
		Set<ZSetOperations.TypedTuple<Integer>> weekRank = redisTemplate.opsForZSet().reverseRangeWithScores(RANK_HOT_WEEK_KEY + weekKey, 0, -1);
		// 进行判断查看有没有数据
		if (!(weekRank == null || weekRank.size() == 0)) {
			// 如果拿到数据直接进行查询并返回
			return getRankHotVOList(weekRank);
		}
		// 如果没有拿到数据集,进行合并操作并存入缓存
		// 获取 day key
		long dayKey = RankKeyUtils.getDayKey();
		// 计算需要进行聚合的上周一的 day key
		long mondayKey = dayKey % 7;
		if (mondayKey >= 4) {
			mondayKey = dayKey - (3 + mondayKey);
		} else {
			mondayKey = dayKey - (10 + mondayKey);
		}
		// 聚合上周的day key,存入集合
		ArrayList<String> dayKeys = new ArrayList<>();
		for (long i = mondayKey; i < mondayKey + 7; i++) {
			dayKeys.add(RANK_HOT_DAY_KEY + i);
		}
		// 合并操作获取合并结果
		redisTemplate.opsForZSet().unionAndStore("COUNT_WEEK", dayKeys, RANK_HOT_WEEK_KEY + weekKey);
		// 设置过期时间为 一星期
		redisTemplate.expire(RANK_HOT_WEEK_KEY + weekKey, RANK_HOT_WEEK_TTL, TimeUnit.SECONDS);
		weekRank = redisTemplate.opsForZSet().reverseRangeWithScores(RANK_HOT_WEEK_KEY + weekKey, 0, -1);
		return getRankHotVOList(weekRank);
	}

这其实和日榜差不多,主要区别就是需要统计几天的数据进行合并操作

上述 return 中方法主要就是根据 id 进行数据项查找

	/**
	 * 拿到博客id及热度后,设置 热度排行榜显示 的相关参数
	 *
	 * @param typedTuples 排行数据
	 * @return 热点排行数据列表
	 */
	private List<RankHotVO> getRankHotVOList(Set<ZSetOperations.TypedTuple<Integer>> typedTuples) {
		if (typedTuples == null || typedTuples.size() == 0) {
			// 如果没有创建,说明今天暂无热榜相关的信息,返回空信息
			return null;
		}
		// 2 如果创建了,封装响应信息
		//拿到set集合迭代器
		Iterator<ZSetOperations.TypedTuple<Integer>> iterator = typedTuples.iterator();
		List<RankHotVO> result = new ArrayList<>();
		// 创建博客id集合
		List<Integer> blogIdList = new ArrayList<>();
		while (iterator.hasNext()) {
			//拿到这项信息
			ZSetOperations.TypedTuple<Integer> tuple = iterator.next();
			// 拿到 blogId
			Integer blogId = tuple.getValue();
			// 2.1 将博客id存入集合
			blogIdList.add(blogId);
			// 2.2 设置热度信息
			RankHotVO rankHotVO = new RankHotVO();
			rankHotVO.setHot(tuple.getScore());
			result.add(rankHotVO);
		}
		List<Blog> blogList = blogMapper.selectBatchIds(blogIdList);
		// 创建 用户id集合
		List<Integer> userIdList = new ArrayList<>();
		for (Blog blog : blogList) {
			// 拿出 userId 存入集合
			userIdList.add(blog.getAuthorId());
		}
		// 查询并设置 user信息
		Map<Integer, UserDTO> userList = userClient.getUserList(userIdList).getData();
		for (int i = 0; i < blogList.size(); i++) {
			// 设置用户相关信息
			result.get(i).setBlog(blogList.get(i));
			result.get(i).setAuthor(userList.get(blogList.get(i).getAuthorId()));
		}

		return result;
	}

性能优化

为了提高效率,可以先对需要查找的 id 进行统计,然后一次性查找出所有的内容,减少数据库压力。

开源项目推荐

最后可以看看我的开源项目: i集大校园(类似于一个定位为校园里的微博)

i集大校园软件服务端,基于SpringCloud Alibaba 微服务组件及部分分布式技术实现服务之间关联及协作进行前后端分离项目实现。计划实现微信小程序和app两端同步。

使用技术栈为:Spring Boot、Spring Cloud Alibaba、rabbitMQ、JWT、minIO、mysql、redis、ES、docker、Jenkins、mybatis-plus

前端使用 Vue3 编写。

欢迎一起参加开源贡献和star项目哈!

使用Redis做实时排行榜 设想在一个游戏中,有上百万的玩家数据,如果现在需要你根据玩家的经验值整理一个前20名的排行榜,你会怎么做呢? 一般的做法是写一条类似下面这条sql语句的方式来获取: select * from game_socre order by score desc limit 0,20 这种方式在数据量较小的情况下可行,但是在数据量大的情况下查询速度将变慢,特别是还需要联表查询时,速度下降的就更明显了。 ... 阅读详情

相关推荐

Redis实战 | 使用Redis 的有序集合(Sorted Set)实现排行榜功能,和Spring Boot集成

Redis 的有序集合(Sorted Set)是一个基于分数(score)排序的数据结构,它在 Redis 中非常重要,常用于实现排行榜、近似计数器等功能。Redis 的有序集合(Sorted Set)是基于跳跃表(Skip List)实现的。跳跃表是一种高效的数据结构,其插入、删除和查找操作的平均时间复杂度都是 O(log n),相对于平衡树(如红黑树)的实现要简单很多。跳跃表的结构类似于链表,每个节点除了保存元素值外,还包含一个指针数组,分别指向对应层次的下一个节点。

AI时代光年 4482

RedisTemplate使用最详解(五)--- opsForZSet()

linux或者mac下启动Nacos报错解决:nohup: /Users/xx/jdk/java/bin/java: No such file or directory

m0_56602396的博客 8401

AI对嵌入式的冲击分三波,大多数人只看到了第一波

摘要:AI对嵌入式行业的冲击将分三波递进:第一波(已发生)是代码生成,AI能完成底层驱动配置;第二波(1-2年内)是架构设计,AI将参与系统方案制定;第三波(2-3年)是需求验证,AI能穷举测试用例但无法替代安全决策。每波冲击都将重新定义工程师的价值标准,经验型工程师若不能将隐性知识转化为显性规则将面临淘汰。作者指出,完全不用AI的人第一波就会被淘汰,而仅满足于第一波应用的人将在后续冲击中落后。

u010898524的博客 960

Redis实现排行榜

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 这里简单说一下每种数据类型,不做深入了解。 string(字符串) string 是 redis 最基本的类型,一个 key 对应一个 value。 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。 string 类型是 Redis 最基本的数据类型,string 类型的值最大能存

admin_mvip的博客 558

UFS电源管理实战:如何优化手机存储的功耗与性能(附实测数据)

本文深入探讨了UFS电源管理的实战优化策略,旨在平衡手机存储的功耗与性能。通过解析UFS核心电压域与工作状态,重点介绍了通过动态调整bActiveICCLevel(活动电流等级)来实现精细控制,并结合实测数据与场景驱动的智能策略,为Android系统与硬件工程师提供了降低功耗、提升流畅度的有效方法。

weixin_29214559的博客 503

CRISPR筛选全流程指南:从文库构建到数据分析实战

功能基因组学研究需要高效解析基因型与表型的关联,高通量筛选技术是其中的关键工具。基于CRISPR系统的筛选方法通过大规模sgRNA文库扰动基因功能,结合慢病毒递送和测序分析,能够系统鉴定影响细胞增殖、耐药、病毒感染的候选基因。与RNAi或化学小分子筛选相比,CRISPR筛选具有覆盖全基因组、脱靶率低、信号清晰等优势,已广泛应用于药物靶点发现、合成致死和免疫治疗机制研究。本文围绕实验设计与操作中的核心要点展开,涵盖文库扩增、病毒滴度控制、MOI把握、筛选模式选择以及基于MAGeCK的sgRNA丰度分析,并针对

weixin_31188927的博客 275

redis实现排行榜(日榜,周榜,月榜)

redis排榜功能的实现

lvyangxue的博客 1万+

【毕业设计】QT实战:从零构建仿QQ通讯系统,打造你的简历亮点项目

本文为计算机专业毕业生提供了一份详尽的毕业设计实战指南,教你如何从零开始使用QT框架构建一个仿QQ的即时通讯系统。文章深入剖析了项目规划、核心模块(登录、聊天、好友管理)的实现、TCP粘包处理、数据库设计等关键技术难点,并分享了如何将此项目打造成简历亮点,有效提升求职竞争力。

omega的博客 442

Redis常用五大数据类型

Redis字符串:

weixin_38568503的博客 1372

循迹小车控制实验:代码集成与硬件验证

在main.c文件中还需定义上层代码调用的GPIO输入和PWM输出相关函数,其中调用STM32CubeMX生成的HAL库函数。}else{}else{}else{}else{在main.h文件中的USER CODE BEGIN EFP注释代码段中外部声明Get函数和Set函数,使该函数可以被上层代码调用。循迹小车控制实验到本章全部完成。相比于前几章节的实验,本实验复杂程度是最高的。

u013288925的博客 125

YOLO11涨点优化:Loss魔改 | WIoU (Wise-IoU) v3版本全面替换,基于动态非单调聚焦机制,让模型更聪明地学习

YOLOv11目标检测中的WIoU v3损失函数改进 本文探讨了YOLOv11目标检测模型中CIoU损失函数的局限性,并提出了WIoU v3改进方案。关键发现包括: CIoU的缺陷:在低质量样本(小目标/遮挡目标)上过度惩罚,导致模型优化方向偏离,小目标AP往往比大目标低30%左右。 WIoU v3的创新: 动态非单调聚焦机制:根据样本"离群度"智能分配梯度 让模型重点优化中等质量样本,减少对低质量样本的无效关注 采用指数移动平均来自适应调整质量评判标准 实际效果:WIoU v3在保持大

2601_95807009的博客 422

【面试高高手】—— Redis

Redis面试题总结

是江迪呀 的博客 1198

【WorkBuddy专栏36】从踩坑到上线——WorkBuddy代码开发避坑指南与部署完全手册

【WorkBuddy专栏36】从踩坑到上线——WorkBuddy代码开发避坑指南与部署完全手册

AI人人懂 1059

使用 Redis Zset 有序集合实现排行榜功能(SpringBoot环境)

使用 Redis Zset 有序集合实现排行榜功能(SpringBoot环境)

kerwin的博客 7249

【AI编程干货】Vibe Coding 技巧(适用于AI编程IDE)

用 AI 写代码这件事门槛低,谁都能对着对话框输入几句话让它生成代码,但生成的东西能不能用、出了问题能不能兜住,这中间的差距比想象中大得多。 我从去年开始把 AI 编码工具作为主力开发手段,中间经历过项目失控、代码雪崩、反复返工,慢慢摸索出一套相对稳定的工作方式。这篇把核心的五个习惯拆开聊聊,都是实际项目里验证过的。

blues_C的博客 621

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

使用Redis Zset实现排行榜

zset底层会使用score作为排序的依据,如果分数相同则字典排序。member作为热搜事件,score作为点击量。以Key作为排行榜的名字,比如热搜等。member :String类型。key:String类型。score:数值类型。

0.0 2098

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Redis如何实现排行榜?

span 用于计算元素排名(rank),例如查找 ele = 肖菜鸡、score = 17 的排名,只需要把查找路径经过的节点的 span 相加即可,如下图的红色路径的 span 累加,按照上面的生成链表方式,每次往上增加一层链表的节点个数是下面一层的一半,这样的查找过程就类似于一个二分查找,时间复杂度为 O(log n)。但是,这种方式在插入数据的时候有很大的问题,每次新增一个节点,就会打乱相邻的两层链表节点个数 2:1 的关系,如果要维持这个关系,就需要对链表调整,事件复杂度是 O(n)。

2301_78526383的博客 6428

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【Redis】应用场景-排行榜(ZSet)

我们将全面解析 Redis 的 Zset 类型。我们将从 Zset 的基本概念和特性开始,然后深入到它的内部实现和性能优化。我们还将通过实际的示例来展示如何在实际应用中使用 Zset。无论你是刚接触 Redis,还是已经有一定经验的开发者,我相信你都能从这篇文章中学到一些有用的知识。​在 Redis中Zset(有序集合)类型可能是最复杂,但也是最强大的一种。Zset 不仅可以存储键值对,还可以为每个元素分配一个分数,然后根据这个分数进行排序。这使得 Zset 非常适合用于实现排行榜、时间线等功能。

精益求精 2868

Springboot整合Redis实现排行榜

Zset是Redis的一种有序集合类型,它与普通集合set非常相似,也是一个没有重复元素的字符串集合。不同之处在于,zset的每个成员都关联了一个评分(score),这个评分被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以重复。我们都知道Redis有五种常用的数据类型分别是:string(字符串),hash(哈希),list(列表),set(集合)及zset (sorted set:有序集合)使用Redis实现排行榜,主要是利用到了zset数据类型的特点。

m0_53434091的博客 774

使用redis实现排行榜功能

项目需要做排行榜功能,实现员工邀请用户注册排行榜,要求是实时更新,查询要快。员工所属支行、二级行、省行,界面要根据条件显示排名数据。

bank8818bank的专栏 1万+

java通过redis实现排行榜功能

java通过redis实现排行榜功能

果酱 の 博客 6745

RedisTemplate操作zset类型

RedisTemplate操作zset类型

H_ker的博客 1734

如何使用Redis实现排行榜?(含同分按时间排序)

Redis的ZSET(有序集合)是实现排行榜的理想数据结构,但当分数相同时,默认会按字典序排序。要实现同分按时间排序,需要特殊设计。

qq_36929638的博客 1167

Redis数据类型——针对实习面试

本文针对即将面试或者实习的同学,总结了Redis数据类型相关的面试题和知识点,希望对同学们有所帮助

清酒伴风的博客 1424

【Redis】Redis zset 实现排行榜

排行榜是业务开发中常见的一个场景,如何设计一个好的数据结构能够满足高效实时的查询,下面我们结合一个实际例子来讨论一下。

DreamSun的博客 3084

redis使用zset实现排行榜功能(取前5位/倒数5位)

redis使用zset实现排行榜功能(取前5位/倒数5位)

weixin_42169734的博客 5875
上一篇: Java后台实现网站微信扫码登录功能,获取用户openid,及微信用户信息(小程序码方案),关联微信小程序(个人主体小程序也可以)
下一篇: 快递鸟API报错 “Reason“: “业务错误[不支持当前快递公司的查询]“解决
durancer
博客等级 码龄6年 5130粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

durancer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值