第十一章 缓存之更新/穿透/雪崩/击穿

news2024/10/6 18:25:51

目录

一、什么是缓存

二、缓存更新策略

2.1. 缓存主动更新策略

2.1.1. Cache Aside模式(主流)‌

2.1.2. Read/Write Through模式‌

2.1‌.3. Write Behind模式‌

2.1.4. 总结

三、缓存穿透

四、缓存雪崩

五、缓存击穿

5.1. 互斥锁实现

5.1.1. 实现流程图

​编辑5.1.2. 主要实现代码


本文中的图片内容部分来源于黑马程序员教程案例

一、什么是缓存

‌缓存是一种用于临时存储数据的技术或机制,旨在提高数据访问速度和系统性能。‌ 缓存通常位于计算机系统内部或附近,可以是硬件、软件或两者的结合体。例如,Web浏览器可以将最常访问的网页内容缓存在本地,以便下次访问时无需从远程服务器重新下载。‌

缓存的作用和原理在于利用程序局部性原理,将频繁访问的数据存储在高速存储器中,如‌SRAM或‌DRAM,以便快速访问。当硬件需要读取数据时,首先在缓存中查找,如果找到则直接执行,否则从内存中查找。由于缓存的运行速度比内存快得多,因此缓存的作用是帮助硬件更快地运行。

缓存可以根据不同的分类标准进行分类。例如,根据存储位置和用途的不同,可以分为‌CPU缓存、‌硬盘缓存、‌内存缓存等。CPU缓存包括‌L1、L2和L3缓存,硬盘缓存用于提高数据传输速度。此外,还有‌HTTP缓存、‌浏览器缓存等。

虽然缓存可以提高系统性能,但也会引入一定的数据一致性问题。因为缓存中的数据可能会与后端数据源(如数据库)存在不一致的情况,所以在使用缓存时需要考虑数据的更新和缓存的过期策略,以保证数据的一致性。

二、缓存更新策略

2.1. 缓存主动更新策略

指在更新数据库中的数据时,如何同步更新缓存中的数据,以保证数据的一致性。常见的缓存更新策略包括‌Cache Aside模式、‌Read/Write Through模式和‌Write Behind模式。

2.1.1. Cache Aside模式(主流)

Cache Aside模式是最常用的缓存更新策略,其中又分为两种:

1. 先删除缓存,再操作数据库

2. 先操作数据库,再删除缓存(主流),其操作步骤如下:

  1. 先更新数据库中的数据。
  2. 然后删除缓存中的数据,或者让缓存失效。
  3. 当下次查询时,如果缓存失效,则重新从数据库中读取数据并更新缓存。

注:由于操作缓存和数据库的话存在线程安全性问题,相较于先删除缓存再操作数据库而言,先操作数据库再删除缓存对于产生线程安全性的概率较低,因为写缓存的速度比更新数据库要快很多:线程1在查询缓存未命中后继续查询数据库时,线程2进来更新数据库,绝大部分情况下线程2更新数据库期间,线程1已经完成了缓存的写入。

2.1.2. Read/Write Through模式

Read/Write Through模式将缓存和数据库整合为一个服务,由服务来维护一致性。操作步骤如下:

  1. 查询操作直接从缓存中读取数据。
  2. 如果缓存中不存在数据,则直接从数据库中读取并返回给用户,同时将数据写入缓存。

2.1‌.3. Write Behind模式

Write Behind模式由其他线程异步地将缓存数据持久化到数据库,保证最终一致性。操作步骤如下:

  1. 更新操作只更新缓存。
  2. 由其他线程异步地将缓存数据写入数据库。

2.1.4. 总结

不同策略的优缺点

Cache Aside模式的优点是简单易实现,但存在数据不一致的风险。Read/Write Through模式的优点是数据一致性高,但性能较低。Write Behind模式的优点是最终一致性,但需要额外的线程管理。

不同场景下的适用性

Cache Aside模式适用于读多写少的场景,简单高效。Read/Write Through模式适用于对数据一致性要求高的场景。Write Behind模式适用于写操作较少,可以容忍最终一致性的场景。

缓存更新策略的最佳实践方案

1. 低一致性需求:使用Redis自带的内存淘汰机制

2. 高一致性需求:主动更新,并以超时剔除作为兜底方案

读操作:

  • 缓存命中则直接返回
  • 缓存未命中则查询数据库,并写入缓存,设定超时时间

写操作:

  • 先写数据库,然后再删除缓存
  • 要确保数据库与缓存操作的原子性

整个写操作的逻辑,我们要确保事务性,如在单体的Spring的JavaWeb项目业务代码的Service实现层添加@Transactional注解,分布式的项目中可以通过TTC模式来控制,当比如删除Redis缓存出现异常,直接回滚数据库的写操作。

三、缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会到数据库。

常见的解决方案有两种:

3.1. 缓存空对象(项目中主流用法):

  • 优点:实现简单,维护方便
  • 缺点:额外的内存消耗以及可能造成短期的不一致

2. 布隆过滤器

  • 优点:内存占用较少,没有多余key
  • 缺点:实现复杂、存在误判可能

3. 参数校验过滤不合法请求

通过对用户输入的参数进行校验,例如检查ID的格式是否合法,如果不合法则直接拒绝请求。这种方法可以过滤掉一部分恶意伪造的用户请求,减少对数据库的压力。

4. 使用分布式锁

在缓存未命中时,通过分布式锁控制只有一个请求去查询数据库,其他请求等待锁释放。这样可以防止多个请求同时查询数据库,减轻数据库的压力。

5. 服务降级和限流

在面对大量的恶意请求时,可以通过服务降级或限流的方式来保护后端服务。限流可以限制到达数据库的请求数量,避免数据库压力过大。

四、缓存雪崩

缓存雪崩是指由于缓存中大量数据同时失效或缓存服务器故障,导致大量请求直接打到数据库上,引发数据库压力激增,可能导致整个系统崩溃的现象。‌ 缓存雪崩通常由于缓存的过期策略不当或缓存服务器故障导致。例如,如果大量的缓存数据设置为在同一时间点过期,或者缓存服务器出现问题无法提供服务,所有的请求将直接访问后端存储系统,导致后端系统瞬时承受巨大的负载压力‌

解决方案:

  • 给不同的key的TTL失效时间设置随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

五、缓存击穿

缓存击穿是指当一个缓存中的热点数据过期或被删除后,大量并发请求同时到达,导致这些请求直接穿透缓存访问数据库,从而增加数据库的负载。‌ 这种情况通常发生在热点数据过期或删除的瞬间,导致短时间内大量请求无法通过缓存获取数据,直接访问数据库,造成数据库负载急剧增加‌。

缓存击穿的具体场景包括:

  • 热点数据过期‌:当缓存中的热点数据过期时,大量请求会同时查询后端数据库,导致数据库负载增加‌。
  • 第一次请求‌:对于一个之前从未被请求过的数据,当它第一次被请求时,缓存中没有该数据,导致请求穿透到后端存储‌。

解决缓存击穿的方法包括:

  • 设置热点数据永不过期‌:将热点数据的缓存过期时间设置为较长的时间,甚至是永不过期。这种方法可以避免缓存击穿,但可能导致数据不够及时和准确‌。
  • 使用互斥锁‌:在数据失效时,通过设置互斥锁来保护数据库访问过程。如果某个请求已经获取到了锁,其他请求需要等待,直到获取到锁为止。这种方法可以避免大量并发请求同时访问数据库,但可能导致并发性能下降和请求等待时间增加‌

注意:这两种方案没有孰优孰劣,在实际项目中,我们要针对业务场景和需求,权衡自身是更注重可用性还是一致性来做选择。

5.1. 互斥锁实现

5.1.1. 实现流程图

互斥锁的实现主要基于Redis的命令setnx再附加一个失效时间,key不存在时可以往Redis中写入(返回值1),否则写入失败(返回值0),同一时期如果有多人写入同一个key,只有一人能成功,以此达到互斥锁的效果:


5.1.2. 主要实现代码

package com.hmdp.service;

import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.extension.service.IService;

public interface IShopService extends IService<Shop> {

    Result queryById(Long id);

    Result update(Shop shop);
}
package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.hmdp.utils.CacheClient;
import com.hmdp.utils.SystemConstants;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.domain.geo.GeoReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.*;

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {


    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        // 互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);
    }

    @Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("id不能为空");
        }
        // 1.更新数据库
        updateById(shop);
        // 2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return Result.ok();
    }

    public Shop queryWithMutex(Long id) {
        String key = CACHE_SHOP_KEY + id;
        // 1. 从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2. 判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3. 存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        // 判断命中的是否为null空值,如果是除了null以外的""和" \t\n"这类空串,则直接返回错误信息
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }
        // 4. 实现缓存重建
        // 4.1. 获取互斥锁
        String lockKey = "lock:shop" + id;
        Shop shop = null;
        try {
            boolean isLock = trylock(lockKey);
            // 4.2. 判断是否获取成功
            if (!isLock) {
                // 4.3. 失败,则休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);
            }

            // 4.4 获取成功,则根据id查询数据库
            shop = getById(id);
            // 5. 不存在,则返回错误
            if (shop == null) {
                // 将空值写入redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            //  6. 存在,写入Redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException();
        } finally {
            // 释放互斥锁
            unlock(lockKey);
        }
        // 7. 返回
        return shop;
    }

    private boolean trylock(String key) {
        // 不要直接返回flag,因这个值可能会为空,直接拆箱会可能报空指针
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
}

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

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

相关文章

好用且不伤眼镜的超声波清洗机排名!谁才是清洁小能手?

对于经常佩戴眼镜的人来说&#xff0c;眼镜的日常清洁保养极为关键。传统清洁方式可能导致镜片刮花和残留污渍&#xff0c;鉴于此&#xff0c;眼镜专用的超声波清洗机应运而生&#xff0c;利用超声振动技术深入微细缝隙&#xff0c;彻底扫除污垢与油脂&#xff0c;保护镜片免受…

计算机编程入门先学什么最好?零基础入门到精通,收藏这篇就够了

看完其他知友的回答&#xff0c;我认为他们的观点过于局限&#xff0c;并没有真正切中问题的要害。 我们不妨换个角度&#xff0c;站在更高一层来看这个问题「计算机编程入门先学什么最好&#xff1f;」 计算机入门最应该学的是 Linux&#xff0c;而非任何的编程语言。 这篇文…

A_H_README_TO_RECOVER勒索恢复---惜分飞

有客户mysql数据库被黑(业务数据库被删除)&#xff0c;创建了一个A_H_README_TO_RECOVER库 [rootwww.xifenfei.com ~]# mysql -uroot -pxxxxx Warning: Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; …

站岗放哨树形dp

前言&#xff1a;好久没有写树上dp了&#xff0c;这儿题目还是挺有意思的 题目地址 #include<bits/stdc.h> #include<iostream> using namespace std;//#define int long long int n; const int N (int)1e510; int e[N],ne[N],h[N],idx 0; int dp[2][N];void add…

【笔记】神领物流Day1.1.20权限管家

传智权限管家是一个通用的权限管理中台服务&#xff0c;在神领物流项目中&#xff0c;我们使用权限系统管理企业内部员工&#xff0c;比如&#xff1a;快递员、司机、管理员等。 在权限管家中可以管理用户&#xff0c;管理后台系统的菜单&#xff0c;以及角色的管理。 权限管家…

【百度文心智能体】想开发爆款智能体?来看看 万圣节之夜探秘者 智能体开发流程大揭秘

目录 前言 一. 创作灵感 二. 智能体中Prompt如何设计 2.1 头像 && 聊天背景 2.2 智能体简介 && 角色定位与目标 2.3 思考路径 && 个性化 2.4 开场白 && 自动追问 2.5 插件选择 三. 总结 前言 从2022年11月底ChatGPT …

JavaEE: 数据链路层的奇妙世界

文章目录 数据链路层以太网源地址和目的地址 类型数据认识 MTU 数据链路层 以太网 以太网的帧格式如下所示: 源地址和目的地址 源地址和目的地址是指网卡的硬件地址(也叫MAC地址). mac 地址和 IP 地址的区别: mac 地址使用6个字节表示,IP 地址4个字节表示. 一般一个网卡,在…

论文选题没思路?用这7个GPT提示词10分钟确定论文选题

选题是论文写作的第一步&#xff0c;也是至关重要的一步。毕业论文选题都是让大学生头疼的大事。没有灵感、方向不清、信息太多&#xff0c;常常让人无从下手。现在有了ChatGPT这样的AI写作辅助工具&#xff0c;它可以帮你快速生成丰富的选题思路&#xff0c;轻松解决选题难题。…

Anaconda创建环境

目录 前言 第一步&#xff1a;更改环境创建位置 第二步&#xff1a;安装环境 前言 在我们创建多个项目的时候&#xff0c;有时会安装的库版本冲突&#xff0c;所以最好是一个项目一个环境 第一步&#xff1a;更改环境创建位置 新安装Anaconda后&#xff0c;在创建环境时环…

洛谷每日一题(P1205 [USACO1.2] 方块转换 Transformations)矩阵变换

原题目链接&#xff1a; P1205 [USACO1.2] 方块转换 Transformations - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 原题目截图&#xff1a; 思路分析&#xff1a; 这题目还是比较简单&#xff0c;模拟一下旋转变化的过程&#xff0c;然后注意变换的规律就行了。 读取输入…

电脑提示msvcp140.dll丢失如何解决,马上教你6种解决方法

在使用电脑时&#xff0c;我们可能会遇到提示缺少msvcp140.dll的错误信息。这个提示意味着我们的电脑中缺少MSVCP140.dll这个文件&#xff0c;它是某些程序运行所必需的。如果我们遇到这个问题&#xff0c;应该如何解决呢&#xff1f;本文将详细解析如何解决msvcp140.dll丢失的…

C. Tree Pruning【Codeforces Round 975 (Div. 1)】

C. Tree Pruning (永远不知道为什么TLE直到把初始化的memset换成for循环 题意很简单&#xff0c;就是找到一个深度&#xff0c;使得删除最少的节点且所有的叶子节点都为这个深度。 从小到大遍历可能的深度i&#xff0c;容易知道所有 深度大于i的节点 和所有 子树最大深度小于i…

vue出现Component name “Politic“ should always be multi-word错误

效果 原因 组件名不能为单个单词&#xff0c;怕和html标签混淆 解决方法 1.选择多个单词区分 2.修改package.json里的rules规则&#xff0c;忽略文件命名校验

详细解释:前向传播、反向传播等

详细解释:前向传播、反向传播等 在机器学习和深度学习中,**前向传播(Forward Propagation)和反向传播(Backward Propagation)**是训练神经网络的两个核心过程。理解这两个概念对于掌握神经网络的工作原理、优化方法以及模型微调技术(如LoRA、P-tuning等)至关重要。以下…

YOLO11改进|注意力机制篇|引入ELA注意力机制

目录 一、【ELA】注意力机制1.1【ELA】注意力介绍1.2【ELA】核心代码 二、添加【ELA】注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【ELA】注意力机制 1.1【ELA】注意力介绍 这篇论文的作者通过分析Coordinate Attention(C…

Python中的数据可视化:从入门到进阶

数据可视化是数据分析和科学计算中的重要环节&#xff0c;它通过图形化的方式呈现数据&#xff0c;使复杂的统计信息变得直观易懂。Python提供了多种强大的库来支持数据可视化&#xff0c;如Matplotlib、Seaborn、Plotly等。本文将从基础到进阶&#xff0c;详细介绍如何使用这些…

如何构建LSTM神经网络模型

一、了解LSTM 1. 核心思想 首先&#xff0c;LSTM 是 RNN&#xff08;循环神经网络&#xff09;的变体。它通过引入细胞状态 C(t) 贯穿于整个网络模型&#xff0c;达到长久记忆的效果&#xff0c;进而解决了 RNN 的长期依赖问题。 2. 思维导图 每个LSTM层次都有三个重要的门结构…

贝尔曼公式

为什么return 非常重要 在选择哪个策略更好的时候&#xff0c;此时需要使用到return&#xff0c;比如下面三个策略的返回值。 策略1: 策略2&#xff1a;策略3&#xff1a;涉及到两个policys path How to calculate return 定义 上图定义了不同的起点下的return value 递推…

优化销售漏斗建立高效潜在客户生成策略的技巧

如何建立有效的潜在客户生成策略&#xff1f;建立有效潜在客户生成策略需要准确定义目标受众&#xff0c;利用内容营销、SEO、社交媒体、邮件营销和定向广告吸引客户&#xff0c;参加行业会议并跟踪分析数据。借助Zoho CRM系统&#xff0c;企业能够更加高效地管理客户信息&…

Windows上 minGW64 编译 libssh2库

下载libssh2库:https://libssh2.org/download/libssh2-1.11.0.zip 继续下载OpenSSL库: https://codeload.github.com/openssl/openssl/zip/refs/heads/OpenSSL_1_0_2-stable