基于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: 8889

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

3.Product商品实体

@Data
public class Product implements Serializable {

	//商品id
    private 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 {

    @Resource
    private 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的时候是不会执行的
     */
    @PostConstruct
    public 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,当前用户id
        userRecentSearch.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的分数+1
        productList.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 {

    @Resource
    private 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 {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private 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实现分布式锁详细方法

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/416152.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP缓存

TCP缓存是指TCP协议在数据传输过程中使用的一种机制&#xff0c;用于临时存储和管理数据包。它主要有三个作用&#xff1a;提高网络性能、保证数据的可靠性和实现流量控制。 首先&#xff0c;TCP缓存可以提高网络性能。当发送端发送数据时&#xff0c;TCP协议会将数据分割成若…

从Spring Boot应用上下文获取Bean定义及理解其来源

前言 在Spring框架中&#xff0c;Bean是组成应用程序的核心单元。特别是在Spring Boot项目中&#xff0c;通过使用SpringApplication.run()方法启动应用后&#xff0c;我们可以获得一个ConfigurableApplicationContext实例&#xff0c;这个实例代表了整个应用程序的运行时环境…

golang使用gorm操作mysql1

1.mysql连接配置 package daoimport ("fmt""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger" )var DB *gorm.DB// 连接数据库&#xff0c;启动服务的时候&#xff0c;init方法就会执行 func init() {username : "roo…

【Unity】导入IAP插件后依赖冲突问题 com.android.billingclient冲突

【Unity】Attribute meta-data#com.google.android.play.billingclient.version 多版本库冲突_unity billingclient-CSDN博客 打开mainTemplate.gradle 找到dependencies { } 在里面末尾加上如下&#xff1a; configurations.all {exclude group: com.android.billingclien…

【奋楫扬帆,赓续前行】中创算力2024年度工作会议

2024年2月28日 【中创算力2024年度工作会议】 在正商国际广场如期举行 全体中创员工齐聚一堂 回首2023年 攻坚克难&#xff0c;再创佳绩 励精图治&#xff0c;创新求强 奋楫扬帆&#xff0c;赓续前行 让我们再回顾 属于中创算力的“高光时刻” &#xff08;政府调研指…

spring boot整合cache使用memcached

之前讲了 spring boot 整合 cache 做 simple redis Ehcache 三种工具的缓存 上文 windows系统下载安装 memcached 我们装了memcached 但spring boot没有将它的整合纳入进来 那么 我们就要自己来处理客户端 java历史上 有过三种客户端 那么 我们用肯定是用最好的 Xmemcached …

MapGIS农业信息化解决方案—共享服务(2)

农业服务手机“小秘书” 农业服务“小秘书”是基于主流智能手机开发的农业服务客户端应用,实现常规农事气象信息、预警信号接收,农业服务产品订阅、专家咨询、农业测土配方施肥建议等功能。农业服务“小秘书”支持目前主流操作系统,普通上网手机通过 Web 即可登陆使用,同时…

数据可视化基础与应用-01-数据可视化概述

总结 本系列是数据可视化基础与应用的第02篇&#xff0c;主要介绍数据可视化概述&#xff0c;包括数据可视化的历史&#xff0c;原理&#xff0c;工具等。 认识大数据可视化 数据是什么 信息科学领域面临的一个巨大挑战是数据爆炸。据IDC Global DataSphere统计&#xff0c…

计讯物联5G RedCap网关TG463化繁为简,推动5G赋能千行百业

5G RedCap&#xff0c;全称为Reduced Capability&#xff0c;即在5G的基础上&#xff0c;对部分功能进行化繁为简后形成的新技术标准&#xff0c;故又称轻量化5G。作为高性价比下的精简技术&#xff0c;5G RedCap技术具备成本低、低功耗、兼顾5G等特点&#xff0c;能够在确保应…

阿里云2024年服务器2核4G配置评测_CPU内存带宽_优惠价格

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

注意!存在49%的软件采购者,要求供应商提供软件SBOM文件!

更多网络安全干货内容&#xff1a;点此获取 ——————— “我们发现&#xff0c;软件系统间接依赖中存在的漏洞数量&#xff0c;是直接依赖的三倍以上。” Snyk 《2020 年开源安全状况报告》中讲到。开源软件中的绝大多数安全漏洞都存在于间接依赖关系中&#xff0c;而不是…

java面试题之nginx篇

1. 什么是Nginx&#xff1f; Nginx是一个 轻量级/高性能的反向代理Web服务器&#xff0c;他实现非常高效的反向代理、负载平衡&#xff0c;他可以处理2-3万并发连接数&#xff0c;官方监测能支持5万并发&#xff0c;现在中国使用nginx网站用户有很多&#xff0c;例如&#xff…

禽类屠宰加工污废水处理需要哪些工艺设备

禽类屠宰加工产生的污废水处理是保护环境并维护生态平衡的重要一环。针对禽类屠宰加工行业的特点&#xff0c;需采用适合的工艺设备来处理污废水。以下是几种常用的工艺设备&#xff1a; 1. 沉淀池&#xff1a;沉淀池是禽类屠宰加工污废水处理的首要设备之一。其作用是将含有悬…

如何使用Logstash搜集日志传输到es集群并使用kibana检测

引言&#xff1a;上一期我们进行了对Elasticsearch和kibana的部署&#xff0c;今天我们来解决如何使用Logstash搜集日志传输到es集群并使用kibana检测 目录 Logstash部署 1.安装配置Logstash &#xff08;1&#xff09;安装 &#xff08;2&#xff09;测试文件 &#xff…

高压高能碳陶瓷无感电阻的制作以及应用?

由于现有需求&#xff0c;许多现代电子电路和设备都会经历瞬态脉冲和浪涌。这反过来又导致需要“设计”瞬态浪涌保护&#xff0c;尤其是在电机控制器等电路中。当电机启动时&#xff0c;此时消耗的电流过大&#xff0c;可能导致电阻器故障。同样&#xff0c;如果电容器用于电机…

基于Python3的数据结构与算法 - 05 堆排序

目录 一、堆排序之树的基础知识 1. 树的定义 2. 树的一些概念 二、堆排序二叉树的基本知识 1. 二叉树的定义 2. 二叉树的存储方式&#xff08;表达方式&#xff09; 2.1 顺序存储方式 三、堆 1. 堆的定义 2. 堆的向下调整性质 四、堆排序的过程 1. 建造堆 五、时…

如何用好应用权限,保护隐私数据?银河麒麟桌面操作系统V10 SP1 2303 update2新功能解析

为您介绍银河麒麟桌面操作系统V10 SP1 2303 update2隐私设置和权限管理功能&#xff0c;为您的个人数据安全保驾护航。 说到个人数据隐私&#xff0c;在科技重塑生活本质的数字世界&#xff0c;个人信息遭受持续威胁。2018年&#xff0c;某国际知名社交平台因安全系统漏洞而遭…

CSS:弹性盒子Flexible Box布局

CSS:Flexible Box弹性盒子布局 一、flex布局原理 ​ flex是flexible Box的缩写,意为 ”弹性布局“&#xff0c;用来为盒状模型提供最大的灵活性&#xff0c;任何一个容器都可以指定为flex布局。 当我们的父盒子设置为flex布局之后&#xff0c;子元素的 float 、clear 和 vert…

抖音无水印视频关键词批量下载操作说明|视频批量采集工具

抖音无水印视频关键词批量下载工具是一款便捷实用的软件&#xff0c;通过关键词搜索功能可以轻松进行视频批量下载。QQ:290615413 以下是操作步骤及功能介绍&#xff1a; 打开软件后进入关键词搜索页面&#xff1a; 在软件中找到第一个选项卡&#xff0c;即为关键词搜索功能。 …

JOISC2022 复制粘贴(区间DP,字符串hash)

题目描述 题面 分析 这道题考场没有任何头绪&#xff0c;赛后也是看了许多题解才明白状态设计和转移的一步步思考过程。 首先我们需要想到 无论是屏幕上的字符串&#xff0c;还是剪切板上的字符串&#xff0c;在任何时候都必须是目标串的子串。这个非常好像&#xff0c;如果不…