黑马点评(二)--商户查询缓存

news2024/11/25 2:50:56

目录

    • 1.缓存更新策略
      • 1.1内存淘汰
      • 1.2超时剔除
      • 1.3主动更新
    • 2.实现缓存和数据库的双写一致
      • 2.1Controller
      • 2.2Service
      • 2.3思路讲解
    • 3.解决缓存穿透问题
      • 3.1出现原因
      • 3.2解决方案
      • 3.3代码实现
    • 4.解决缓存雪崩问题
      • 4.1出现原因
      • 4.2解决方案
      • 4.3代码实现
    • 5.解决缓存击穿问题
      • 5.1出现原因
      • 5.2解决方案
      • 5.3代码实现
        • 5.3.1互斥锁(try Lock + double Check)
        • 5.3.2逻辑过期

1.缓存更新策略

1.1内存淘汰

redis自动进行,当内存达到我们设置的最大内存的时候,redis就会触发淘汰机制,淘汰掉一些不重要的数据

适用情况:对数据一致性要求不高

1.2超时剔除

给redis中的key设置ttl过期时间,当达到过期时间以后会自动剔除过期的数据

适用情况:对数据一致性要求一般

1.3主动更新

在编写业务逻辑的时候,修改了数据库中的数据后,同时更新缓存中的数据

适用情况:对数据一致性要求高

2.实现缓存和数据库的双写一致

在这里插入图片描述

2.1Controller

    /**
     * 更新商铺信息
     *
     * @param shop 商铺数据
     * @return 无
     */
    @PutMapping
    public Result updateShop(@RequestBody Shop shop) {
        // 写入数据库
        return shopService.updateShop(shop);
    }
    
    /**
     * 根据id查询商铺信息
     *
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return shopService.getShopById(id);
    }

2.2Service

    /**
     * 更新商铺信息
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-28
     * @since V1.0
     */
    @Override
    public Result updateShop(Shop shop) {
        // 修改数据
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        updateById(shop);
        // 删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return Result.ok();
    }

    /**
     * 根据id查询商铺信息
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-28
     * @since V1.0
     */
    @Override
    public Result getShopById(Long id) {
        // 1.从缓存中查询
        String shopStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 2.缓存中有数据直接返回
        if (!StringUtil.isNullOrEmpty(shopStr)) {
            Shop shop = JSONUtil.toBean(shopStr, Shop.class);
            return Result.ok(shop);
        }
        // 3.缓存中无数据查数据库
        Shop shop = this.getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        // 4.数据库中数据同步到缓存
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
        // 5.返回数据库中数据
        return Result.ok(shop);
    }

2.3思路讲解

实现缓存和数据库双写一致,思路就是主动更新+超时剔除兜底。首先我们先操作数据库,然后删除缓存;查询的时候,先从缓存中查询,如果有值直接返回,如果没有值则去数据库中查询,没有值则返回数据为空,有值则将查到的值存入redis中,并设置过期时间,最后返回查到的数据。

3.解决缓存穿透问题

3.1出现原因

客户端请求的数据在数据库和缓存中都不存在,这样的请求都会打到数据库,因此就出现穿透现象。

3.2解决方案

1、在请求到达redis之前,加一层布隆过滤器(可能会出现误判)
2、在redi中缓存空对象

3.3代码实现

修改service层代码,缓存空对象

/**
     * 根据id查询商铺信息
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-28
     * @since V1.0
     */
    @Override
    public Result getShopById(Long id) {
        // 1.从缓存中查询
        String shopStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 2.缓存中有数据直接返回
        if (!StringUtil.isNullOrEmpty(shopStr)) {
            Shop shop = JSONUtil.toBean(shopStr, Shop.class);
            return Result.ok(shop);
        }
        // 3.缓存中无数据查数据库
        Shop shop = this.getById(id);
        if (shop == null) {
            // 3.1数据库中不存在数据缓存空对象
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(new Shop()), 3, TimeUnit.MINUTES);
            return Result.fail("店铺不存在!");
        }
        // 4.数据库中数据同步到缓存
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
        // 5.返回数据库中数据
        return Result.ok(shop);
    }

4.解决缓存雪崩问题

4.1出现原因

redis中大量的key同一时间内失效或者redis服务器宕机,导致大量的请求直达数据库,给数据库带来巨大的压力。

4.2解决方案

1、给redis中的key设置不同的ttl过期时间,防止大量的key同时失效
2、给redis服务器设置集群,预防某个redis服务器出现宕机情况

4.3代码实现

修改service层代码,给redis中的key设置不同的ttl过期时间

    /**
     * 根据id查询商铺信息
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-28
     * @since V1.0
     */
    @Override
    public Result getShopById(Long id) {
        // 1.从缓存中查询
        String shopStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 2.缓存中有数据直接返回
        if (!StringUtil.isNullOrEmpty(shopStr)) {
            Shop shop = JSONUtil.toBean(shopStr, Shop.class);
            return Result.ok(shop);
        }
        // 3.缓存中无数据查数据库
        Shop shop = this.getById(id);
        if (shop == null) {
            // 3.1数据库中不存在数据缓存空对象
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(new Shop()), RandomUtil.randomLong(2, 3), TimeUnit.MINUTES);
            return Result.fail("店铺不存在!");
        }
        // 4.数据库中数据同步到缓存
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RandomUtil.randomLong(20, 30), TimeUnit.MINUTES);
        // 5.返回数据库中数据
        return Result.ok(shop);
    }

5.解决缓存击穿问题

5.1出现原因

在高并发场景下,redis中的热点key失效,导致无数请求直达数据库,给数据库带来巨大的冲击。

5.2解决方案

1、使用互斥锁(try Lock + double Check)
2、逻辑过期

5.3代码实现

5.3.1互斥锁(try Lock + double Check)

首先查询缓存,如果有数据直接返回,如果没有数据则去获取互斥锁(setnx),如果获取到锁,则重建缓存,返回数据库中查到的数据,如果获取不到锁,则休眠等待一段时间,重新去获取锁(获取锁之前再查一遍缓存),直到获取到锁或者从缓存中查到数据为止。

/**
     * 根据id查询商铺信息
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-28
     * @since V1.0
     */
    @Override
    public Result getShopById(Long id) {
        String lockkey = "lock:shop:" + id;
        Shop shop = new Shop();
        // 1.从缓存中查询
        String shopStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);

        // 2.缓存中有数据直接返回
        if (!StringUtil.isNullOrEmpty(shopStr)) {
            System.out.println(Thread.currentThread().getName()+"从缓存中获取到了数据");
            shop = JSONUtil.toBean(shopStr, Shop.class);
            return Result.ok(shop);
        }

        // 3.缓存中无数据获取互斥锁
        Boolean isGetLock = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "1", 10, TimeUnit.SECONDS);
        try {
            if (isGetLock) {
                System.out.println(Thread.currentThread().getName()+"获取到锁");
                // 3.1.获取到锁查询数据库重建缓存
                shop = this.getById(id);
                if (shop == null) {
                    // 数据库中不存在数据缓存空对象
                    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(new Shop()), RandomUtil.randomLong(2, 3), TimeUnit.MINUTES);
                    return Result.fail("店铺不存在!");
                }
                // 数据库中数据同步到缓存
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RandomUtil.randomLong(20, 30), TimeUnit.MINUTES);
                System.out.println(Thread.currentThread().getName()+"重建了缓存");
            } else {
                System.out.println(Thread.currentThread().getName()+"没有获取到锁");
                // 3.2.获取不到锁,休眠一段时间,重新获取
                while (!isGetLock) {
                    Thread.sleep(1000);
                    return this.getShopById(id);
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            stringRedisTemplate.delete(lockkey);
        }

        // 4.返回数据库中数据
        System.out.println(Thread.currentThread().getName()+"从数据库中获取到了数据");
        return Result.ok(shop);
    }
5.3.2逻辑过期

提前缓存好热点key吗,热点key设置为永不过期,在key对应的值中存入数据和过期的时间(当前时间+多久后过期);从缓存中查到数据首先判断是否为空,如果为空则返回数据为空,如果不为空,判断缓存中的数据是否过期,如果没过期直接返回缓存中的数据,如果过期了则去获取锁,获取到锁,新开一个线程重建缓存,(主线程)然后返回过期的数据,如果没有获取到锁,则直接返回过期的数据。

    /**
     * 利用单元测试提前保存缓存中的热点key
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-29
     * @since V1.0
     */
    public void saveRedisHotKey(Long shopId, Long expireTime) {
        // 1、创建存储数据和过期时间的实体类
        RedisData redisData = new RedisData();

        redisData.setData(getById(shopId));
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireTime));
        // 2、将实体类放入缓存,不设置过期时间
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + shopId, JSONUtil.toJsonStr(redisData));
    }

/**
     * 根据id查询商铺信息
     *
     * @author lichuancheng
     * @date 创建时间 2024-04-28
     * @since V1.0
     */
    @Override
    public Result getShopById(Long id) {
        String lockkey = "lock:shop:" + id;
        // 1.从缓存中查询
        String shopStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);

        // 2.缓存中没有数据直接返回店铺不存在
        if (StringUtil.isNullOrEmpty(shopStr)) {
            System.out.println(Thread.currentThread().getName() + "查询了缓存中不存在的店铺");
            return Result.fail("店铺不存在!");
        }

        // 3.缓存中有数据判断是否过期
        RedisData redisData = JSONUtil.toBean(shopStr, RedisData.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        if (!LocalDateTime.now().isAfter(expireTime)) {
            // 3.1未过期直接返回缓存中的数据
            System.out.println(Thread.currentThread().getName() + "返回了缓存中未过期的数据");
            return Result.ok(shop);
        } else {
            // 3.2过期了获取锁
            Boolean isGetLock = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "1", 10, TimeUnit.SECONDS);
            if (isGetLock) {
                System.out.println(Thread.currentThread().getName() + "获取到了锁");
                // 3.2.1获取到锁新开线程重建缓存,返回过期的数据
                shopServiceThreadPool.submit(() -> {
                            // 重建缓存(只能使用lambda表达式调用,因为lambda表达式的this指向当前类,如果使用匿名内部类,则this指向匿名内部类)
                            try {
                                this.saveRedisHotKey(id, 10l);
                                System.out.println(Thread.currentThread().getName() + "重建了缓存");
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            } finally {
                                stringRedisTemplate.delete(lockkey);
                            }
                        }
                );

                System.out.println(Thread.currentThread().getName() + "获取到锁并返回了缓存中过期的数据");
                // 返回过期数据
                return Result.ok(shop);
            } else {
                // 3.2.2没获取到锁,直接返回过期的数据
                System.out.println(Thread.currentThread().getName() + "未获取到锁并返回了缓存中过期的数据");
                return Result.ok(shop);
            }
        }
    }

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

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

相关文章

Spring Cloud Security Oauth2 授权码模式

授权码存取—内存方式 获取Code Bisic认证 WebSecurityConfig 配置 Basic Auth认证 登录 数据库建表 授权码存储方式-数据库 Beanpublic AuthorizationCodeServices authorizationCodeServices() {return new JdbcAuthorizationCodeServices(dataSource);}问题 OAuth2 授…

rocketmq dashboard控制台中topic状态无法展示

现象 在使用rocketmq控制台查看topic状态和订阅状态时,出现错误和没有信息的情况。 原因 rocketmq控制台版本问题,最新版本为1.0.1,支持rocketmq5版本,如果使用rocketmq4版本的服务无法兼容对应的数据。同理1.0.0版本也无法兼容ro…

C++ 抽象机制

抽象机制 1. 虚函数 使用关键字virtual 声明的函数,意思是可能随后在其派生类中重新定义。 纯虚函数 在声明的末尾使用0 的函数,说明是纯虚函数。 抽象类 含有纯虚函数多的类称为抽象类(abstract class). 多态类型 如果一个类负责为其他一些类提供接…

C语言例题31:在屏幕上显示一个菱形

题目要求&#xff1a;在屏幕上显示一个菱形 #include <stdio.h>void main() {int i, j;int x;printf("输入菱形行数(3以上的奇数&#xff09;&#xff1a;");scanf("%d", &x);//显示菱形上面的大三角形for (i 1; i < (x 1) / 2; i) {for (…

重磅合作:OpenAI将金融时报的数据引入ChatGPT|TodayAI

在今天的重磅公告中&#xff0c;金融时报&#xff08;FT&#xff09;与OpenAI宣布建立了一项战略合作伙伴关系和许可协议。这一举措标志着金融时报将其卓越新闻内容引入ChatGPT平台&#xff0c;同时也为FT读者带来前所未有的AI新体验。 这项合作不仅让ChatGPT用户在查询时能够…

Oracle 表分区

1.概述 分区表就是将表在物理存储层面分成多个小的片段&#xff0c;这些片段即称为分区&#xff0c;每个分区保存表的一部分数据&#xff0c;表的分区对上层应用是完全透明的&#xff0c;从应用的角度来看&#xff0c;表在逻辑上依然是一个整体。 目的&#xff1a;提高大表的查…

redis中缓存穿透问题

缓存穿透 缓存穿透问题&#xff1a; 一般请求来到后端&#xff0c;都是先从缓存中查找数据&#xff0c;如果缓存中找不到&#xff0c;才会去数据库中查询数据。 而缓存穿透就是基于这一点&#xff0c;不断发送请求查询不存在的数据&#xff0c;从而使数据库压力过大&#xff…

自然语言处理 (NLP) 和文本分析

自然语言处理 (NLP) 和文本分析&#xff1a;NLP 在很多领域都有着广泛的应用&#xff0c;如智能助手、语言翻译、舆情分析等。热门问题包括情感分析、命名实体识别、文本生成等。 让我们一起来详细举例子的分析讲解一下自然语言处理&#xff08;NLP&#xff09;和文本分析的应用…

Java对接高德api搜索POI 2.0 关键字搜索

目录 一、注册账号 二、搜索小demo 1.首先要引入依赖 2. 然后查看打印结果即可 三、搜索接口代码 1.引入依赖 2.yml配置 2.Controller 3.静态工具类 四、运行测试 一、注册账号 高德开放平台 | 高德地图API 注册高德开发者&#xff1b;去控制台创建应用&#xff…

使用 ArcGIS 对洪水预测进行建模

第一步 — 下载数据 所有数据均已包含在 Esri 提供的项目压缩文件中。我将创建一个名为“Stowe_Hydrology.gdb”的新地理数据库,在其中保存这些数据以及创建的所有后续图层。 1-0。斯托市边界 斯托城市边界是佛蒙特州地理信息中心提供的矢量要素类面。我将这一层称为“Stow…

【Leetcode每日一题】 综合练习 - 找出所有子集的异或总和再求和(难度⭐)(68)

1. 题目解析 题目链接&#xff1a;1863. 找出所有子集的异或总和再求和 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路与实现 为了求解给定整数数组的所有子集并将其异或和相加&#xff0c;我们可以采用递…

速成python

一个只会c的苦手来总结一下py的语法。没有其他语法基础的不建议看 1. 输入输出 print自带换行&#xff0c;可以写print("Hi", end"")取消换行 a input(你好:) # 默认是str print(type(a)) # 输出a的类型 a int(input()) # 或者a int(a) print(type(…

大气污染扩散模型Calpuff技术应用

目前&#xff0c;大气污染仍为我国亟待解决的环境问题。为了弄清大气污染物排放后对周围环境的影响&#xff0c;需要了解污染物的扩散规律。Calpuff模型是一种三维非稳态拉格朗日扩散模型&#xff0c;可有效地处理非稳态&#xff08;如&#xff0c;熏烟、环流、地形和海岸等&am…

UE C++ 链表

目录 概要单链表双向链表头插入尾插入中间插入删除查找 小结 概要 链表 简单说明&#xff0c;链表有单链表&#xff0c;双向链表&#xff0c;循环链表(本篇文章以UE c代码说明)。链表的操作&#xff0c;插入&#xff0c;删除&#xff0c;查找。插入&#xff0c;删除效率高&…

【Redis | 第十篇】Redis与MySQL保证数据一致性(两种解决思路)

文章目录 10.Redis和MySQL如何保证数据一致性10.1双写一致性问题10.2数据高度一致性10.3数据同步允许延时10.3.1中间件通知10.3.2延迟双删 10.Redis和MySQL如何保证数据一致性 10.1双写一致性问题 Redis作为缓存&#xff0c;它是如何与MySQL的数据保持同步的呢&#xff1f;特…

泽攸科技无掩膜光刻机在MEMS压力传感器制造中的应用

在当今的科技快速发展时代&#xff0c;微电子机械系统&#xff08;MEMS&#xff09;技术已成为推动现代传感器技术革新的关键力量。MEMS压力传感器&#xff0c;作为其中的重要分支&#xff0c;广泛应用于生物医学、航空航天、汽车工业等多个领域。随着对传感器性能要求的不断提…

网络工程专业考研的方向有哪些?

前言 网络工程专业的学生在考研时可选择的专业或方向包括&#xff1a;物联网、计算机网络技术、信息安全、信息与通信工程等。 1&#xff09;物联网&#xff1a;“物联网就是物物相连的互联网”&#xff0c;有两层意思&#xff1a;第一&#xff0c;物联网的核心和基础仍然是互…

【C++干货基地】探索C++模板的魅力:如何构建高性能、灵活且通用的代码库(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

2024.4.23 LoadRunner 测试工具详解 —— VUG

目录 引言 LoadRunner 三大组件之间的关系 LoadRunner 脚本录制 启动并访问 WebTours 脚本录制 编译 运行&#xff08;回放&#xff09; LoadRunner 脚本加强 事务插入 插入集合点 插入检查点 参数化 ​编辑 打印日志 引言 问题&#xff1a; 此处为啥选择使用 Lo…

算法设计与分析4.1 迷宫问题 栈与队列解法、打印矩阵、三壶问题、蛮力匹配

1.ROSE矩阵 实现&#xff1a; 使用算法2 分析&#xff1a; 每半圈元素值的增长规律变换一次 设增量为t&#xff0c;每半圈变换一次t <— -t . 设矩阵边长为i&#xff0c;每半圈的元素个数是2*(i-1)个&#xff0c;hc为记数变量&#xff0c;则1≤hc<2i-1&#xff0c;前1/…