当前位置: 首页 > news >正文

基于redis实现【最热搜索】和【最近搜索】功能

目录

  • 一、前言
  • 二、分析问题
  • 三、针对两个问题,使用redis怎么解决问题?
    • 1、字符串String
    • 2、列表List
    • 3、字典Hash
    • 4、集合Set
    • 5、有序集合ZSet
    • 6、需要解决的五大问题
  • 四、编写代码
    • 1.pom依赖
    • 2.application.yml配置
    • 3.Product商品实体
    • 4.用户最近搜索信息
    • 5.redis辅助类SearchRedisHelper
    • 6.业务service
    • 7.controller控制层
  • 五、postman测试
    • 1.第一次搜索
    • 2.热点搜索
    • 3.最近搜索
    • 4.第二次第三次搜索
    • 5.再看热点搜索
    • 6.再看最近搜索变化
    • 7.第四次搜索
    • 8.热搜变化
    • 9.最近搜索变化
  • 六、总结

一、前言

大家在浏览各种网站,比如淘宝,京东,微博等网站,都会看到一些热门搜索最近搜索的功能,大家有木有好奇,技术背后是如何实现的呢?今天我们一起来用redis解决这两个问题,并已在项目中实战!!!
热搜如下图:

在这里插入图片描述
最近搜索如下图:

在这里插入图片描述

二、分析问题

1、热门搜索:是指一定时间内、一定范围内,公众较为关心的热点问题,被搜索的次数越多,热搜榜越靠前。

2、最近搜索:只显示当前用户最近一段时间内的搜索记录,按照时间进行排序,如果有重复搜索,覆盖到重复的数据,并且要排到最前面。

3、针对于热门的搜索属于高并发的场景,还需要高性能显示给用户,用MySQL存储显然不太合适,流量过多会把MySQL撑爆,最近搜索和最热搜索也不需要持久化,最好的解决方案之一就是redis做缓存,单机redis可以承受10万QPS

三、针对两个问题,使用redis怎么解决问题?

我们复习一下redis的五大数据类型,redis数据类型可以参考Redis中5种基本数据类型结构详解

1、字符串String

特性:
(1)最基本的数据类型,二进制安全的字符串,最大512M
(2)支持字符串操作:strlen或取value的长度,返回的是字节的数量。
(3)数据交互有个二进制安全的概念,给我数据的时候你自己编码,字节数组到达我这里整理,帮你存,客户端之间商量好。
(4)支持数值计算操作:incr,decr
应用场景:做简单得键值对缓存,比如`Session,token,统计,限流,轻量级(kb级别)的FS内存级的文件系统—任何东西都可以变成字节数组(二进制),一些复杂的计数功能的缓存

2、列表List

特性:
按照添加顺序保持顺序的字符串列表,也就是存储一些列表型得数据结构,类似粉丝列表、文字得评论列表之类得数据。
应用场景:
可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。

3、字典Hash

特性:
(1)key-value对的一种集合,存储结构化得数据,比如一个对象。
(2)这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。
应用场景:
经常会用来做用户数据的管理,存储用户的信息。比如做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

4、集合Set

特性:
无序的字符串集合,不存在重复的元素.
应用场景:
去重,还可以利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

5、有序集合ZSet

特性:
已排序的字符串集合。去重并排序,如获取排名前几名。
应用场景:
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。

6、需要解决的五大问题

问题一:很显然根据咱们的以上分析,热门搜索和最近搜索的功能需要去重并且排序,热门搜索点击率最高的在前面,最近搜索最新的数据搜索在最前面,所以使用ZSet集合实现最合适。针对于最近搜索的功能使用List也可以实现,但是删除的效率要比ZSet慢,还需要自己去重,所以还是Zset最合适。

问题二:用户可能无限制浏览商品,最近搜索的功能需要确保zSet 不能无限制插入,需要控制zSet 的大小,也就是指保存最近N条浏览记录。

问题三:最近搜索的功能需要在插入第N+1 条后移除最开始浏览的第一条。

问题四:热门搜索key值需要过期时间的。

问题五:热门搜索针对的是所有用户,而最近搜索针对的是当前用户。

以上五大问题均在代码中详细解决,仔细看注释。

四、编写代码

1.pom依赖

<!-- redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.application.yml配置

server:port: 8889spring:redis:host: 127.0.0.1port: 6379password: database: 2timeout: 5000

3.Product商品实体

@Data
public class Product implements Serializable {//商品idprivate Long id;//商品名称private String productName;//.....等属性
}

4.用户最近搜索信息

@Data
public class UserRecentSearch implements Serializable {/*** 搜索信息*/private String searchInfo;/*** 用户id*/private Long unionId;
}

5.redis辅助类SearchRedisHelper

@Component
public class SearchRedisHelper {@Resourceprivate RedisTemplate redisTemplate;/*** 热搜的KEY*/public static final String HOT_SEARCH = "product_hot_search";/*** 最近搜索的KEY*/public static final String RECENT_SEARCH = "product_recent_search";/*** 最近搜索的大小*/public static final Integer CURRENT_SEARCH_SIZE = 3;/*** 最热搜索KEY过期时间*/public static final Integer HOT_SEARCH_EXPIRE_TIME = 3;/*** 设置redis的过期时间* expire其实是懒加载,不设置key的时候是不会执行的*/@PostConstructpublic void setHotSearchExpireTime() {redisTemplate.expire(HOT_SEARCH, HOT_SEARCH_EXPIRE_TIME, TimeUnit.SECONDS);}/*** redis添加最近搜索** @param query*/public void addRedisRecentSearch(String query) {UserRecentSearch userRecentSearch = new UserRecentSearch();// 用户id,当前用户iduserRecentSearch.setUnionId(100434L);// 搜索信息userRecentSearch.setSearchInfo(query);// score为一个分值,需要把最近浏览的商品id的分值设为最大值,// 此处我们可以设置为当前时间Instant.now().getEpochSecond()// 这样最近浏览的商品id的分值一定最大,排在Zset集合最前面ZSetOperations<String, UserRecentSearch> zSet = redisTemplate.opsForZSet();// 由于zSet的集合特性当插入已经存在的V值(商品id)时只会更新score值,zSet.add(RECENT_SEARCH, userRecentSearch, Instant.now().getEpochSecond());// 获取到全部用户的最近搜索记录,用reverseRangeWithScores方法,可以获取到根据score排序之后的集合Set<ZSetOperations.TypedTuple<UserRecentSearch>> typedTuples = zSet.reverseRangeWithScores(RECENT_SEARCH, 0, -1);//只得到当前用户的最近搜索记录,注意这里必须保证set集合的顺序Set<UserRecentSearch> userRecentSearches = listRecentSearch();if (userRecentSearches.size() > CURRENT_SEARCH_SIZE) {//获取到最开始浏览的第一条UserRecentSearch userRecentSearchLast = userRecentSearches.stream().reduce((first, second) -> second).orElse(null);//删除最开始浏览的第一条zSet.remove(RECENT_SEARCH, userRecentSearchLast);}}/*** 热搜列表* @return*/public Set<Product> listHotSearch() {//0 5 表示0-5下标对应的元素return redisTemplate.opsForZSet().reverseRangeWithScores(HOT_SEARCH, 0, 5);}/*** redis添加热搜* @param productList*/public void addRedisHotSearch(List<Product> productList) {//1:表示每调用一次,当前product的分数+1productList.forEach(product -> redisTemplate.opsForZSet().incrementScore(HOT_SEARCH, product, 1D));}/*** 最近搜索列表* @return*/public Set<UserRecentSearch> listRecentSearch() {Set<ZSetOperations.TypedTuple<UserRecentSearch>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(RECENT_SEARCH, 0, -1);return Optional.ofNullable(typedTuples).map(tuples -> tuples.stream().map(ZSetOperations.TypedTuple::getValue).filter(Objects::nonNull)
//                        .filter(userRecentSearch -> Objects.equals(userRecentSearch.getUnionId(), ContextHolder.getUser().getId())).filter(userRecentSearch -> Objects.equals(userRecentSearch.getUnionId(), 100434L)).collect(Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new), LinkedHashSet::new))).orElseGet(LinkedHashSet::new);}}

6.业务service

@Service
public class ProductService {@Resourceprivate SearchRedisHelper searchRedisHelper;/*** 搜索* @param query* @return*/public List<Product> search(String query) {//业务代码可用es.....此处略过....模拟数据库数据List<Product> productList = new ArrayList();Product product = new Product();product.setId(1L);product.setProductName("iphone15");productList.add(product);searchRedisHelper.addRedisRecentSearch(query);searchRedisHelper.addRedisHotSearch(productList);return productList;}/*** 热搜列表* @return*/public Set<Product> listHotSearch() {return searchRedisHelper.listHotSearch();}/*** 最近搜索列表* @return*/public Set<UserRecentSearch> listRecentSearch() {return searchRedisHelper.listRecentSearch();}
}

7.controller控制层

@RequestMapping("/redis/test")
@RestController
public class RedisController {@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate ProductService productService;/*** 删除redis* @param key* @return*/@GetMapping("/w/remove/redis")public Result removeRedis(String key){redisTemplate.delete(key);return Result.success();}/*** 搜索* @param query* @return*/@GetMapping("/r/search/product")public Result listProduct(String query) {return Result.success(productService.search(query));}/*** 热搜列表* @return*/@ResponseBody@GetMapping("/r/list/hot/search")public Result listHotSearch() {return Result.success(productService.listHotSearch());}/*** 最近搜索列表* @return*/@ResponseBody@GetMapping("/r/list/recent/search")public Result recentHotSearch() {return Result.success(productService.listRecentSearch());}}

五、postman测试

1.第一次搜索

在这里插入图片描述

2.热点搜索

在这里插入图片描述

3.最近搜索

在这里插入图片描述

4.第二次第三次搜索

在这里插入图片描述

在这里插入图片描述

5.再看热点搜索

在这里插入图片描述

6.再看最近搜索变化

在这里插入图片描述

7.第四次搜索

再搜索两次
在这里插入图片描述

8.热搜变化

在这里插入图片描述

9.最近搜索变化

在这里插入图片描述

六、总结

本文针对于网站热点搜索和最近搜索的问题,对redis的五大数据类型进行了解读,并且采用高并发利器redis的ZSet有序集合完美解决本文一开始引入的问题,保证了系统的高并发和高性能,提高用户体验。

项目代码:Github

推荐好文:
Redis实现分布式锁详细方法

如果看到这里,说明你喜欢这篇文章,请转发,点赞

相关文章:

基于redis实现【最热搜索】和【最近搜索】功能

目录 一、前言二、分析问题三、针对两个问题&#xff0c;使用redis怎么解决问题&#xff1f;1、字符串String2、列表List3、字典Hash4、集合Set5、有序集合ZSet6、需要解决的五大问题 四、编写代码1.pom依赖2.application.yml配置3.Product商品实体4.用户最近搜索信息5.redis辅…...

1.2 debug的六种指令的使用,四个通用寄存器

汇编语言 首先进入环境 mount c d:masm //把c挂载在d盘中的masm当中 c: //进入c&#xff0c;进入到编译环境 dir //查看文件&#xff0c;可有可无Debug是DOS、Windows都提供的实模式&#xff08;8086 方式&#xff09;程序的调试工具。使用它可以查看CPU各种寄存器中的内容…...

C# OpenVINO Crack Seg 裂缝分割 裂缝检测

目录 效果 模型信息 项目 代码 数据集 下载 C# OpenVINO Crack Seg 裂缝分割 裂缝检测 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-02-29T16:35:48.364242 author&#xff1a;Ultralytics task&#xff1a;segment version&…...

前后端项目-part03

文章目录 5.4.4 机构名称5.4.4.1 创建实体类Company5.4.4.2 创建实体类CompanyMapper5.4.4.3 创建实体类CompanyService5.4.4.4 创建实体类CompanyController5.4.4.5 后端测试5.4.4.6 修改basic.js5.4.4.7 修改course.vue5.4.4.8 测试5.4.5 课程标签5.4.5.1 效果5.4.5.2 修改co…...

Java 1.8 docker 镜像制作

文章目录 一、下载文件二、精简JRE三、Dockerfile四、构建镜像五、容器测试 一、下载文件 glibc 下载地址 glibc-2.33-r0.apk glibc-bin-2.33-r0.apk glibc-i18n-2.33-r0.apk rsa sgerrand.rsa.pub jre 1.8 jre-8u201-linux-x64.tar.gz 二、精简JRE 解压 tar -zxvf jre-8…...

python中自定义报错

class MyError(Exception):def __init__(self,num):#录入的数Exception.__init__(self)self.numnumdef __str__(self):return 这是我定义的第%d个异常 %(self.num)使用 try:raise MyError(4) except MyError as e:print(e)raise 其作用是指定抛出的异常名称&#xff0c;以及异常…...

part1:sora技术

1.Sora能力边界探索 从sora的视频合集里看到了多段视频&#xff0c;假如我不知道这是sora视频合计&#xff0c;估计我第一反应并不是AI生成了这些视频&#xff0c;可以说在我这里通过了图灵测试&#x1f60a;。 在视频合集里还有同一场景的多角度/镜头的生成能力&#xff0c;让…...

RK3568平台开发系列讲解(基础篇)文件私有数据

🚀返回专栏总目录 文章目录 一、文件私有数据二、文件私有数据实验沉淀、分享、成长,让自己和他人都能有所收获!😄 一、文件私有数据 Linux 中并没有明确规定要使用文件私有数据,但是在 linux 驱动源码中,广泛使用了文件私有数据,这是 Linux 驱动遵循的“潜规则”,实…...

跨时钟信号处理方法

1. 背景 现在的芯片&#xff08;比如SOC&#xff0c;片上系统&#xff09;集成度和复杂度越来越高&#xff0c;通常一颗芯片上会有许多不同的信号工作在不同的时钟频率下。比如SOC芯片中的CPU通常会工作在一个频率上&#xff0c;总线信号&#xff08;比如DRAM BUS&#xff09;会…...

OD(13)之Mermaid饼图和象限图

OD(13)之Mermaid饼图和象限图使用详解 Author: Once Day Date: 2024年2月29日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Mermaid使用指南_Once_day的博客-CSDN博客 参考文章: 关于 Mermaid | Mermaid 中文网 (nodejs.cn)Mermaid | Diagramming and charting tool‍‌⁡…...

基于springboot+vue的智能无人仓库管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…...

图神经网络实战——图论

图神经网络实战——图论 0. 前言1. 图属性1.1 有向图和无向图1.2 加权图与非加权图1.3 连通图非连通图1.4 其它图类型 2. 图概念2.1 基本对象2.2 图的度量指标2.2 邻接矩阵表示法 3. 图算法3.1 广度优先搜索3.2 深度优先搜索 小结系列链接 0. 前言 图论 (Graph theory) 是数学…...

【PHP进阶】Rabbitmq的实际使用

RabbitMQ是一个流行的消息队列中间件&#xff0c;它提供了可靠的消息传递机制。在使用RabbitMQ时&#xff0c;有几个重要的概念需要了解&#xff1a; 消息队列&#xff08;Message Queue&#xff09;&#xff1a;RabbitMQ中的核心概念之一。它是消息的缓冲区&#xff0c;用于存…...

如何解决机器视觉高速图像处理软件的加密需求?

高速图像处理在机器视觉中的应用重要性 在机器视觉行业中&#xff0c;高速图像处理软件的作用至关重要&#xff0c;它使得机器能够迅速分析和处理成千上万的图像数据。这种能力在制造业、安防系统、交通监控等多个领域发挥着核心作用&#xff0c;如在制造业中&#xff0c;高速…...

Linux的条件变量

条件变量 条件变量本身不是锁&#xff0c;但是它可以造成线程阻塞。通常于互斥锁配合使用。给多线程提供一个会和的场合。 使用互斥量保护共享数据使用条件变量可以造成线程阻塞&#xff0c;等待某个条件的发生&#xff0c;当条件满足的时候解除阻塞。 条件变量的两个动作&a…...

【Python笔记-设计模式】状态模式

一、说明 状态模式是一种行为设计模式&#xff0c;用于解决对象在不同状态下具有不同行为 (一) 解决问题 在对象行为根据对象状态而改变时&#xff0c;规避使用大量的条件语句来判断对象的状态&#xff0c;提高系统可维护性 (二) 使用场景 当对象的行为取决于其状态&#…...

Pytorch 复习总结 5

Pytorch 复习总结&#xff0c;仅供笔者使用&#xff0c;参考教材&#xff1a; 《动手学深度学习》Stanford University: Practical Machine Learning 本文主要内容为&#xff1a;Pytorch 卷积神经网络。 本文先介绍了 Pytorch 语法汇总&#xff1a; Pytorch 张量的常见运算、…...

Codeforces Round 930 (Div. 2)

Codeforces Round 930 (Div. 2) Codeforces Round 930 (Div. 2) A. Shuffle Party 题意&#xff1a; 给出长度为n的整数数组a&#xff0c; a i a_i ai​ i&#xff0c;对于k>2的下标进行运算&#xff0c;设d为k除本身外最大的除数&#xff0c; 操作为交换( a k a_k ak​…...

c语言求平方与倒数序列的部分和

本题要求对两个正整数m和n&#xff08;m≤n&#xff09;编写程序&#xff0c;计算序列和m21/m(m1)21/(m1)⋯n21/n。 输入格式: 输入在一行中给出两个正整数m和n&#xff08;m≤n&#xff09;&#xff0c;其间以空格分开。 输出格式: 在一行中按照“sum S”的格式输出部分和…...

Vue-4

自定义创建项目 目标&#xff1a;基于 VueCli 自定义创建项目架子 大致步骤&#xff1a; 安装脚手架创建项目 vue create 项目名称选择自定义 选择 Manually select features 这一项 step-1:按下空格 : 选择/取消--勾选请选择&#xff1a;Babel、Router、CSS、Linterstep-2…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...