聊聊服务端缓存那些事(预热、淘汰、污染、雪崩、穿透、击穿等)

news2025/1/10 2:25:07

文章目录

    • 概要
    • 一、缓存预热
    • 二、缓存污染
        • 2.1、先更新数据库再更新缓存
        • 2.2、先更新缓存再更新数据库
        • 2.3、先删除缓存再更新数据库,读时再更新
        • 2.4、先更新数据库再删除缓存,读时再更新
        • 2.5、缓存污染总结
        • 2.6、删除缓存失败了怎么办?
        • 2.7、延迟双删
        • 2.8、旁路缓存(Cache-Aside)
  • 三、缓存淘汰
    • 四、缓存失效
        • 4.1、缓存雪崩
        • 4.2、缓存穿透
        • 4.3、缓存击穿
    • 五、热点缓存
    • 六、多级缓存
    • 七、小结

概要

对于服务端同学来说,缓存是一个避不开的话题,相信大多数同学接触缓存是在操作系统的课上。缓存就是将低效存储介质(比如磁盘)的内容,临时在高效存储介质(比如内存)中保存一份,以提高读写效率。当然了,这个高效低效是相对的,比如CPU中还有L1/2/3级别缓存,可以说其是计算机内存的缓存。

在服务端,使用缓存主要目的:
1)降低服务器(通常是数据库)压力,进而提高接口响应速度;
2)降低硬件成本,使用缓存可以替代原本需要多台数据库服务器才能承载的请求量。

但在我们引入缓存的同时提高了软件的复杂性,比如要关注缓存失效、污染、淘汰等问题,因此在维护软件服务端过程中引入缓存也是要慎重的,不能当做银弹,如果可以通过提高硬件(CPU、I/O)性能解决问题的时候,那升级硬件往往是更合适的解决方案。

本文主要聊基于数据库的缓存,当然,也可以举一反三到其它场景。
缓存介质以Redis为例,数据库以MySQL为例

简单缓存示意图

一、缓存预热

缓存预热是指服务上线时,提前将相关的业务数据进行构造后写到缓存介质中,避免由于请求量太大而使刚上线的服务压力太大,甚至打垮。

一般来说我们用户第一次读时加载缓存就足够了,那什么情况下使用预加载合适呢:

  1. 缓存内容构造复杂,数据量大;
  2. 热点数据。

二、缓存污染

在聊缓存污染之前,先说下缓存更新的方法:

  1. 缓存预热+写数据库是更新
  2. 读时缓存不存在更新,依赖写时删除
  3. 定时任务,周期性更新缓存,一般适合跑数据的任务,或者列表型的数据,而且不要求绝对实时性。

缓存污染是指缓存与数据库数据不一致。这种不一致常常由开发者更新缓存不规范造成的,更新缓存分为四种:

  1. 先更新数据库再更新缓存;
  2. 先更新缓存再更新数据库;
  3. 先删除缓存再更新数据库,读时再更新;
  4. 先更新数据库再删除缓存,读时再更新。

注意我们是在更新数据库,更新/删除缓存一定成功的条件下讨论

2.1、先更新数据库再更新缓存

我们考虑先后来了a,b两个写请求,要对数据X进行更新。

  • T1:a更新数据库中的X=A;
  • T2:b更新数据库中的X=B;
  • T3:b更新缓存中的X=B;
  • T4:a更新缓存中的X=A;
    经过以上的一番操作数据库和缓存出现了不一致,此时缓存中X=A,数据库中X=B。

2.2、先更新缓存再更新数据库

我们考虑先后来了a,b两个写请求,要对数据X进行更新。

  • T1:a更新缓存中的X=A;
  • T2:b更新缓存中的X=B;
  • T3:b更新数据库中的X=B;
  • T4:a更新数据库中的X=A;
    经过以上的一番操作数据库和缓存出现了不一致,此时缓存中X=B,数据库中X=A。

2.3、先删除缓存再更新数据库,读时再更新

我们考虑当前数据库和缓存中的X=B,先后来了a写请求要对数据X进行更新,b读请求读取X的值。

  • T1:a删除缓存中的X=A;
  • T2:b读取X,缓存不存在;
  • T3:b从数据库读取X=B,并将X=B写入缓存;
  • T4:a更新数据库中的X=A;
    经过以上的一番操作数据库和缓存出现了不一致,此时缓存中X=B,数据库中X=A。

2.4、先更新数据库再删除缓存,读时再更新

我们考虑当前数据库中的X=A,缓存中X不存在,先后来了a读请求读取X的值,b写请求要对数据X进行更新。

  • T1:a读取X,缓存不存在,从数据库读取X=A;
  • T2:b更新数据库中的X=B;
  • T3:b删除缓存中的X;
  • T4:a将X=A写入缓存;
    经过以上的一番操作数据库和缓存出现了不一致,此时缓存中X=A,数据库中X=B。

2.5、缓存污染总结

通过对以上四种方案的分析,都有可能导致缓存不一致。这还没考虑数据库和缓存写失败,那怎么办呢?
那么就要结合实际进行分析了:

  1. 通常来说互联网项目中读的比例远高于写的;
  2. 通常来说读数据库比写数据库效率高,因为写数据库四会加锁的而读一般不需要;
  3. 通常来说缓存的写效率远高于数据库。

基于以上三点共识:

  • 对于[先更新数据库再更新缓存]来说,先后a,b请求来的时候,通常来说a,b请求操作步骤耗时一样(更新数据库和更新缓存),那么网络稍微一抖动,可能就会出现2.1讨论的情况;
  • 对于[先更新缓存再更新数据库]来说,同上,可能就会出现2.2讨论的情况;
  • 对于[先删除缓存再更新数据库,读时再更新]来说,先后a,b请求来的时候,a删缓存效率很高,此时b就发现缓存不存在,但a写数据库很慢,b读数据库是快的,所以很容易出现b读完了数据库a还没更新好数据库的情况,也容易出现2.3讨论的情况;
  • 对于[先更新数据库再删除缓存,读时再更新]来说,先后a,b请求来的时候,首先a读取X要X不存在,由于【互联网项目中读的比例远高于写的】,所以它的概率很低。就算满足了,然后就是a从数据库读取X值后,其写入缓存的速度要慢于b更新数据库的速度,概率更低了,因为【缓存的写效率远高于数据库】

综上 先更新数据库再删除缓存,读时再更新 方案是最优的,真实项目中往往也采用这种方案。

2.6、删除缓存失败了怎么办?

我们通过2.1-2.5小节讨论都没考虑【更新数据库,更新/删除缓存】的情况,针对最后胜出的 【先更新数据库再删除缓存,读时再更新】讨论下。

更新数据库失败的话,直接报错即可。
删除缓存失败的话:

  1. 针对缓存,我们往往是会设置过期时间的,如果对数据库与缓存不一致容忍性高的话可以执行安全失败策略,即直接返回成功即可,等待缓存自动过期或被其他请求删除;
  2. 重试策略。这里列出三种:1)程序内重试若干次(一般3次为宜)。2)解耦,删除失败后放入消息队列,进行异步删除。3)解耦,异步更新缓存,即采用 databus 或者阿里的 canal 监听MySQL binlog 进行更新。

2.7、延迟双删

针对【先更新数据库再删除缓存,读时再更新】方案,为了避免2.4小节讨论的异常情况发生,可以考虑在删除缓存成功后,等待若干时间(一般1s),再执行一次删除缓存操作,这样可以极大的降低异常概率发生,可以当做不可能事件了。当然,这个等待若干时间可以程序内起一个定时任务,也可以使用消息队列延迟队列。

2.8、旁路缓存(Cache-Aside)

旁路缓存就是【先更新数据库再删除缓存,读时再更新】方案。
1:读操作:先读缓存,若命中直接返回,否则读数据库后写入缓存再返回
旁路缓存读操作
2:写操作:写数据库,再删除对应的缓存
旁路缓存写操作
除了旁路缓存策略,还有Read-Through/Write-Through或者Write-Behind(Write-Back),见文章

三、缓存淘汰

缓存的使用主要是为了降低数据库压力,提高响应速度,同时缓存的成本也是高昂的。所以随着缓存机制的运行,一些缓存内容命中率很低或不会再命中,就可以淘汰了。常见的淘汰指标有:
1)基于空间:设置缓存空间大小,到达阈值开始淘汰;
2)基于容量:设置缓存存储记录数,到达阈值开始淘汰;
3)基于时间,到达阈值开始淘汰
TTL(Time To Live,即存活期)缓存数据从创建到过期的时间。
TTI(Time To Idle,即空闲期)缓存数据多久没被访问的时间。

淘汰算法细化有很多:

  • FIFO:先进先出。在这种淘汰算法中,达到阈值(空间大小or记录数),则先进入缓存的会先被淘汰。这种可谓是最简单的了,但是会导致我们命中率很低。试想一下我们如果有个访问频率很高的数据是所有数据第一个访问的,而那些不是很高的是后面再访问的,那这样就会把我们的首个数据但是他的访问频率很高给挤出。

  • LRU:最近最少使用算法。其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。在这种算法中避免了上面的问题,每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。但是这个依然有个问题,如果有个数据在 1 个小时的前 59 分钟访问了 1 万次(可见这是个热点数据),再后一分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。

  • LFU:最近最少频率使用。在这种算法中又对上面进行了优化,利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。

  • TTL:设置过期时间,即给缓存key设置过期时间,到期就被淘汰。实现手段有:1)读的时间判断下是否过期,过期就删除缓存记录再返回空、2)定时任务,检查key是否过期,过期就删除(每个key都一个定时任务[时间轮算法]?还是一个定时任务删一批缓存记录)。Redis就是二者结合使用的。

  • TTI: 设置空闲期,即给缓存key设置空闲期,每被访问一次就延期。

四、缓存失效

4.1、缓存雪崩

4.2、缓存穿透

4.3、缓存击穿

五、热点缓存

六、多级缓存

七、小结

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

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

相关文章

【数据结构与算法】十大经典排序算法-堆排序

🌟个人博客:www.hellocode.top 🏰Java知识导航:Java-Navigate 🔥CSDN:HelloCode. 🌞知乎:HelloCode 🌴掘金:HelloCode ⚡如有问题,欢迎指正&#…

JZ33二叉搜索树的后序遍历序列

题目地址:二叉搜索树的后序遍历序列_牛客题霸_牛客网 题目回顾: 解题思路: 使用栈 栈的特点是:先进后出。 通读题目后,我们可以得出,二叉搜索树是左子节点小于根节点,右子节点大于根节点。 …

wps设置一键标题字体和大小

参考 wps设置一键标题字体和大小:https://www.kafan.cn/A/7v5le1op3g.html 统一一键设置

[FPGA IP系列] 2分钟了解FPGA中的BRAM

FPGA设计中,BRAM是一项非常关键的内置存储资源,FPGA开发需要熟练使用BRAM,今天再复习一下BRAM的知识,包括BRAM的定义、组成、应用等等。 一、BRAM介绍 1、BRAM的定义 RAM是Random Access Memory,也就是随机访问数据…

C字符串练习题(6.3.1)

编写一个程序&#xff0c;从键盘上读入一个小于1000的正整数&#xff0c;然后创建并输出一个字符串&#xff0c;说明该整数的值。例如&#xff0c;输入941&#xff0c;程序产生的字符串是“Nine hundred and forty one”。 #include<stdlib.h> #include<string.h>…

【STM32】FreeRTOS互斥量学习

互斥量&#xff08;Mutex&#xff09; 互斥量又称互斥信号量&#xff08;本质也是一种信号量&#xff0c;不具备传递数据功能&#xff09;&#xff0c;是一种特殊的二值信号量&#xff0c;它和信号量不同的是&#xff0c;它支持互斥量所有权、递归访问以及防止优先级翻转的特性…

Spring Boot 项目实现 Spring AOP

【注】实现在SpringBoot项目中&#xff0c;同时给两个类的方法添加AOP前置通知 1、创建一个SpringBoot项目 2、创建两个目标类和方法 package com.tqazy.learn_spring_project.spring_aop;import org.springframework.stereotype.Service;/*** ClassName SpringAopUserServi…

【树状数组优化哈希DP】CF1801 C

Problem - C - Codeforces 思路&#xff1a; Code&#xff1a; #include <bits/stdc.h>#define lowbit(x) (x & (-x))using i64 long long;constexpr int N 2e5 10; constexpr int mod 1e9 7;std::vector<int> V[N];int n, m, x, mxv 0; int a[N], id[N…

MySQL入门学习教程(一)

mysql简介 1、什么是数据库 &#xff1f; 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库&#xff0c;它产生于距今六十多年前&#xff0c;随着信息技术和市场的发展&#xff0c;特别是二十世纪九十年代以后&#xff0c;数据管理不再仅仅…

学习笔记整理-JS-02-基本类型

文章目录 一、数据类型简介和检测1. JavaScript中两大数据类型 二、基本数据类型1. 数字类型2. 字符串类型3. 布尔类型4. undefined类型5. null 三、数据类型的转换1. 数据类型的转换 四、重点内容 一、数据类型简介和检测 1. JavaScript中两大数据类型 基本数据类型 Number S…

Android学习之路(3) 布局

线性布局LinearLayout 前几个小节的例程中&#xff0c;XML文件用到了LinearLayout布局&#xff0c;它的学名为线性布局。顾名思义&#xff0c;线性布局 像是用一根线把它的内部视图串起来&#xff0c;故而内部视图之间的排列顺序是固定的&#xff0c;要么从左到右排列&#xf…

最强自动化测试框架Playwright(22)-模拟器

可以使用测试生成器通过仿真生成测试&#xff0c;以便为特定窗口、设备、配色方案生成测试&#xff0c;以及模拟地理位置、语言或时区。测试生成器还可以生成测试&#xff0c;同时保留经过身份验证的状态。 模拟视口大小 Playwright 打开一个浏览器窗口&#xff0c;其视口设置…

电路基础之电容

电容器&#xff08;Capacitor&#xff09;是由两个导体电极之间夹着一个电介质而组成的元件。这两个电极可以是金属板、箔片、涂层等&#xff0c;而电介质则是放置在电极之间的绝缘材料。电容器的基本构成包括以下几个要素&#xff1a; 电极&#xff1a;电容器的电极是两个导体…

无涯教程-Perl - readpipe函数

描述 该函数将EXPR作为命令执行。然后,将输出作为标量文本中的多行字符串返回,或者将行作为列表context中的单个元素返回。 语法 以下是此函数的简单语法- readpipe EXPR返回值 此函数在标量context中返回String,在列表context中返回List。 例 以下是显示其基本用法的示…

HTML详解连载(6)

HTML详解连载&#xff08;6&#xff09; 专栏链接 [link](http://t.csdn.cn/xF0H3)下面进行专栏介绍 开始喽CSS特性继承性注意 层叠性特点 优先级规则公式注意 叠加计算公式&#xff08;每以及之间不存在进位&#xff09;规则 Emmet写法分析属性名属性值注意 背景图平铺方式属性…

【分布式存储】数据存储和检索~B+树

为什么数据存储结构重要 在存储系统中&#xff0c;其实不管数据是什么样的&#xff0c;归根结底其实都还是取决于数据的底层存储结构&#xff0c;而主要常见的就是数据库索引结构&#xff0c;B树、Redis中跳表、以及LSM、搜索引擎中的倒排索引。本质都是如何利用不用的数据结构…

群辉nas看剧设置

首先打开NAS的后台页面&#xff0c;打开“控制面板” 然后依次点开“文件服务--SMB--高级设置”&#xff0c;在最小SMB协议后面的方框选择“SMB1"&#xff0c;然后点击”保存“按钮即可&#xff0c;这里这样设置的原因是因为还有很多旧设备只支持SMB1&#xff08;我几年前…

章节7:Burp Intruder模块

章节7&#xff1a;Burp Intruder模块 参考资料 https://portswigger.net/burp/documentation/desktop/tools/intruder 01 Intruder模块作用与原理 原理 http://xxx.xx.com/bbs/index.php?namewuyanzu&mottogo 对请求参数进行修改&#xff0c;分析响应内容&#xff0…

腾讯:海量小文件场景下CephFS优化之路

Ceph开源社区 2021-02-25 17:58 摘自&#xff1a;https://mp.weixin.qq.com/s/rTNyzY9W3ZunroYo57tjoA 1. 背景 随着大数据、人工智能技术的蓬勃发展&#xff0c;人类对于算力资源的需求也迎来大幅度的增长。在腾讯内部&#xff0c;星辰算力平台以降本增效为目标&#xff0c;…