【案例实战】SpringBoot整合Redis实现缓存分页数据查询

news2024/10/6 4:02:28

正式观看本文之前,设想一个问题,高并发情况下,首页列表数据怎么做?

在这里插入图片描述

类似淘宝首页,这些商品是从数据库中查出来的吗?答案肯定不是,在高并发的情况下,数据库是扛不住的,那么我们要怎么去扛住C端端大并发量呢,这快我们可以借助Redis,我们知道Redis是一个基于内存的NoSQL数据库。学过操作系统我们都知道,内存要比磁盘的效率大的多,那我们Redis就是基于内存的,而数据库是基于磁盘的。

我们现在知道要用Redis去做首页数据的分页,那么我们应该用Redis的那种数据结构来做呢。

Redis有5种基本的数据结构,我们这里用list类型做分页。

在 Redis 中,List(列表)类型是按照元素的插入顺序排序的字符串列表。你可以在列表的头部(左边)或者尾部(右部)添加新的元素。

ok,那么接下来我们就通过一个案例实操一下,首页热点数据怎么放到Redis中去查询。

SpringBoot整合RedisTemplate这里就不做过多介绍啦,大家可以网上找篇博文 整合一下。

<!-- 创建SpringBoot项目加入redis的starter依赖 -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

编写ProductService,定于数据分页方法。

public interface ProductService {

    Map<String,Object> productListPage(int current, int size) throws InterruptedException;

}

编写ProductServiceImpl实现类。

/**
 * @author lixiang
 * @date 2023/6/18 21:01
 */
@Service
@Slf4j
public class ProductServiceImpl implements ProductService {

    private static final String PRODUCT_LIST_KEY = "product:list";

    private static final List<Product> PRODUCT_LIST;

    //模拟从数据库中查出来的数据
    static {
        PRODUCT_LIST = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            Product product = new Product();
            product.setId(UUID.randomUUID().toString().replace("-", ""));
            product.setName("商品名称:" + i);
            product.setDesc("商品描述:" + i);
            product.setPrice(new BigDecimal(i));
            product.setInventory(2);
            PRODUCT_LIST.add(product);
        }
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Map<String, Object> productListPage(int current, int size) throws InterruptedException {

        //从缓存中拿到分页数据
        List<Product> productList = getProductListByRedis(current, size);

        if (productList == null || productList.size() == 0) {
            log.info("当前缓存中无分页数据,当前页:" + current + ",页大小:" + size);
            //从数据库中拿到分页数据
            productList = getProductListByDataSource(current, size);
        }
        Map<String, Object> resultMap = new HashMap<>();
        //计算当前总页数
        int totalPage = (PRODUCT_LIST.size() + size - 1) / size;
        resultMap.put("total", PRODUCT_LIST.size());
        resultMap.put("data", productList);
        resultMap.put("pages", totalPage);
        return resultMap;
    }

    private List<Product> getProductListByRedis(int current, int size) {
        log.info("从Redis取出商品信息列表,当前页:" + current + ",页大小:" + size);
        // 计算总页数
        int pages = pages(size);
        // 起始位置
        int start = current <= 0 ? 0 : (current > pages ? (pages - 1) * size : (current - 1) * size);
        // 终止位置
        int end = start+size-1;
        List<Product> list = redisTemplate.opsForList().range(PRODUCT_LIST_KEY, start, end);
        List<Product> productList = list;
        return productList;
    }

    /**
     * 获取商品信息集合
     *
     * @return
     */
    private List<Product> getProductListByDataSource(int current, int size) throws InterruptedException {
        //模拟从DB查询需要300ms
        Thread.sleep(300);
        log.info("从数据库取出商品信息列表,当前页:" + current + ",页大小:" + size);
        // 计算总页数
        int pages = pages(size);
        // 起始位置
        int start = current <= 0 ? 0 : (current > pages ? (pages - 1) * size : (current - 1) * size);
        //数据缓存到redis中
        redisTemplate.opsForList().rightPushAll(PRODUCT_LIST_KEY, PRODUCT_LIST);
        //设置当前key过期时间为1个小时
        redisTemplate.expire(PRODUCT_LIST_KEY,1000*60*60, TimeUnit.MILLISECONDS);
        return PRODUCT_LIST.stream().skip(start).limit(size).collect(Collectors.toList());
    }

    /**
     *  获取总页数
     * @param size
     * @return
     */
    private Integer pages(int size){
        int pages = PRODUCT_LIST.size() % size == 0 ? PRODUCT_LIST.size() / size : PRODUCT_LIST.size() / size + 1;
        return pages;
    }
}

ok,然后编写controller,进行测试。

@RestController
@RequestMapping("/api/v1/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/page")
    public Map<String,Object> page(@RequestParam("current") int current,@RequestParam("size") int size){
        Map<String, Object> stringObjectMap;
        try {
            stringObjectMap = productService.productListPage(current, size);
        } catch (InterruptedException e) {
            stringObjectMap = new HashMap<>();
        }
        return stringObjectMap;
    }
}

当第一次访问的时候,先去Redis中查询,发现没有,然后就去查DB,将要缓存的数据页放到Redis中。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第二次访问的时候。就直接访问Redis啦

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通过Redis和DB查询的对比,我们发现从Redis中拿出来只用了18ms,从公DB中需要300ms,由此可见Redis的一个强大之处。

那么我们观察一下查询逻辑,会不会有什么问题。

    public Map<String, Object> productListPage(int current, int size) throws InterruptedException {

        //从缓存中拿到分页数据
        List<Product> productList = getProductListByRedis(current, size);

        if (productList == null || productList.size() == 0) {
            log.info("当前缓存中无分页数据,当前页:" + current + ",页大小:" + size);
            //从数据库中拿到分页数据
            productList = getProductListByDataSource(current, size);
        }
    }

设想,假如某一时刻,Redis中的缓存失效啦,大量的请求,全部查到DB上,也会带来一个灾难。所以这快又涉及到一个缓存击穿的问题。

解决缓存击穿

  • 方案一:永不过期
    • 提前把热点数据不设置过期时间,后台异步更新缓存。
  • 方案二:加互斥锁或队列
    • 其实我理解缓存击穿和缓存穿透差不多,所以加一个互斥锁,让一个线程正常请求数据库,其他线程等待即可(这里可以使用线程池来处理),都创建完缓存,让其他线程请求缓存即可。

OK,整篇的案例整合 我们就到这里,觉得博主写的不错的,记得给个三连哦!!!

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java解析XML文件(DOM4J解析xml文件)

内容重点1&#xff1a;DOM4J创建、解析、修改XML文件 内容重点2&#xff1a;DOM4J模拟解析web.xml配置文件&#xff0c;实现实例化servlet 1、什么是xml Xml(Extensible Markup Language) 一种扩展性标示语言,出现的意义其实与JSON字符串的意义相似,是新一代的数据交换标准…

怎么加密共享文件夹?局域网共享文件夹加密方法

相信很多企业都在使用局域网共享文件夹&#xff0c;它可以很方便地实现各部门之间的沟通协作。但是由于使用人员众多&#xff0c;数据安全非常难以得到保障。那么我们该怎么加密局域网共享文件夹呢&#xff1f; 共享文件夹加密 首先&#xff0c;我们先来了解一下共享文件夹加密…

某农业大学数据结构A-第13周作业

1.拓扑排序 【问题描述】 拓扑排序的流程如下&#xff1a; 1. 在有向图中选一个没有前驱的顶点并且输出之&#xff1b; 2. 从图中删除该顶点和所有以它为尾的弧。 重复上述两步&#xff0c;直至全部顶点均已输出&#xff0c;或者当前图中不存在无前驱的顶点为止。后一种情况则…

渲染模式差异和选择

传统服务端渲染 在过去传统开发中&#xff0c;页面渲染任务是由服务端完成的&#xff0c;服务器负责获取数据&#xff0c;拼装页面&#xff0c;客户端仅负责内容显示&#xff0c;使用这种方式的典型技术有 JSP、PHP、ASP.NET 等等。 客户端渲染 CSR Vue.js、React 这类框架之…

专访蘑菇物联沈国辉:做工业AI时代的推动者

在中国制造迈向高质量发展的进程中&#xff0c;数智化转型会成为破局之道。在这其中&#xff0c;蘑菇物联除了是一个本分的答题者&#xff0c;还是一个善于发现问题的贡献者。 作者|思杭 编辑|皮爷 出品|产业家 从广州造纸厂到广州造船厂&#xff0c;从第一橡胶厂到电池厂&a…

利用R语言通过百度地图API进行批量地理编码

利用R语言通过百度地图API进行批量地理编码 当您有大量的地点名称需要在地图上来呈现时&#xff0c;首先要在在线地图上找到该地址的坐标&#xff0c;通常是指经纬度&#xff0c;如果能够用代码来实现&#xff0c;便少了许多费时费力的体力活儿&#xff0c;以下将详细介绍地理…

数据结构的一些总结---利用Python实现

大家好&#xff0c;我是北山啦&#xff0c;本文简单介绍Python数据结构的相关内容&#xff0c;简单就是很简单的那种 文章目录 查找线性查找 O(n)二分查找(Binary Search) O(logn) 排序排序Low B三人组冒泡排序选择排序插入排序 排序NB三人组快速排序归并排序 数据结构栈和队列…

[Day 3 of 17]Building a document scanner in OpenCV

a computer vision-powered document scanner 计算机视觉驱动的文档扫描仪&#xff0c;三个步骤&#xff1a; 边缘检测edges通过边缘&#xff0c;找到代表待扫描纸张的轮廓contour应用透视转换(a perspective transform)获得文档自上而下的视图 How to Build a Kick-Ass Mob…

录音转文字的方法有哪些?这三个录音转文字的方法有哪些

你是否曾经遇到过这样的场景&#xff1a;在开会或者采访时&#xff0c;需要记录重要信息&#xff0c;但是手写记录或打字速度跟不上对话节奏&#xff0c;甚至难以记录所有细节。此时&#xff0c;录音转文字软件就派上用场了。然而&#xff0c;市场上有太多种选择&#xff0c;到…

【Vue3 生态】Vue Router 路由知识概览

前言 在 Web 前端开发中&#xff0c;路由是非常重要的一环&#xff0c;但是路由到底是什么呢&#xff1f; 从路由的用途上讲 路由是指随着浏览器地址栏的变化&#xff0c;展示给用户不同的页面。 从路由的实现原理上讲 路由是URL到函数的映射。它将 URL 和应用程序的不同部分…

低代码平台——提高研发效率的神器

一、前言 听起来像是一个噱头&#xff0c;但是低代码确实是一个能够快速提高研发效率的神器。 通过使用低代码平台&#xff0c;研发人员可以节省好几个月的时间&#xff0c;将前后端各种功能可视化以搭积木的形式快速完成。今天&#xff0c;我们将深入探讨低代码开发平台的特点…

两阶段目标检测指南:R-CNN、FPN、Mask R-CNN

动动发财的小手&#xff0c;点个赞吧&#xff01; Source[1] 多阶段&#xff08;Two-stage&#xff09;物体检测 计算机视觉中最基本和最广泛研究的挑战之一是目标检测。该任务旨在在给定图像中绘制多个对象边界框&#xff0c;这在包括自动驾驶在内的许多领域非常重要。通常&am…

一种看门狗复位电源的电路

如下是一个看门狗复位电源的电路&#xff1a; 满足掉电能被监测到&#xff0c;掉电后利用法拉电容保持后级模组继续工作一段时间。 看门狗可以关闭电源 320ms 20ms &#xff0c;对后级模组起到断电复位作用。 同时复位电源拉低的是VCC_4V 而非超级电容所在的VCC_4V_IN,并不会…

SpringBoot项目的事务实现

说明&#xff1a;当业务中的某个功能&#xff0c;需要多个操作组合执行才能完成时&#xff08;如删除部门&#xff0c;部门下的员工也需要同步删除时&#xff09;&#xff0c;为了保证数据的一致性&#xff0c;需要对这些组合操作添加事务。&#xff08;参考&#xff1a;http:/…

从电源 LED 读取智能手机的秘密?

研究人员设计了一种新的攻击方法&#xff0c;通过记录读卡器或智能手机打开时的电源 LED&#xff0c;使用 iPhone 摄像头或商业监控系统恢复存储在智能卡和智能手机中的加密密钥。 众所周知&#xff0c;这是一种侧信道攻击。 通过密切监视功耗、声音、电磁辐射或执行操作所需…

一、elasticsearch的简介与安装

目录 一、Elasticsearch下载 二、安装 三、启动 四、安装可视化插件&#xff08;elasticsearch-head&#xff09; 1、下载地址 2、解压缩下载好的压缩文件 3、进入解压缩目录 五、解决跨域问题 Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方…

端午节出行的小贴士——行之安,乐之逍

亲爱的朋友们&#xff0c; 随着端午节的到来&#xff0c;想必许多人已经开始期待那份出游的快乐与解脱。无论你是期待漫步在宁静的田野小径&#xff0c;还是在繁华的城市中探索&#xff0c;这篇文章都会给你一些实用的端午节出行建议&#xff0c;帮助你尽情享受旅程。 首先&a…

Airtest图像识别测试工具原理解读最佳实践 | 京东云技术团队

1 Airtest简介 Airtest是一个跨平台的、基于图像识别的UI自动化测试框架&#xff0c;适用于游戏和App&#xff0c;支持平台有Windows、Android和iOS。Airtest框架基于一种图形脚本语言Sikuli&#xff0c;引用该框架后&#xff0c;不再需要一行行的写代码&#xff0c;通过截取按…

如何利用数据化营销助力新零售企业发展?”

​“新零售”这个概念诞生至今已有5年&#xff0c;但对于其具体的定义&#xff0c;行业内仍然有许多争议。有人认为“新零售”是对传统零售模式的颠覆&#xff1b;也有人认为“新零售”就是将线上和线下相结合。不论如何&#xff0c;在这个不断变化的行业中&#xff0c;新零售企…

使用omp技术实现wordcount算法

【问题描述】 编写程序统计一个英文文本文件中每个单词的出现次数&#xff08;词频统计&#xff09;&#xff0c;并将统计结果按单词字典序输出到屏幕上。 注&#xff1a;在此单词为仅由字母组成的字符序列。包含大写字母的单词应将大写字母转换为小写字母后统计。 【输入形…