分布式缓存的基本原理

article/2025/9/21 22:27:55

随着互联网的发展,用户规模和数据规模越来越大,对系统的性能提出了更高的要求,缓存就是其中一个非常关键的组件,从简单的商品秒杀,到全民投入的双十一,我们都能见到它的身影。

分布式缓存首先也是缓存,一种性能很好但是相对稀缺的资源,和我们在课本上学习的CPU缓存原理基本相同,CPU是用性能更好的静态RAM来为性能一般的DRAM加速,分布式缓存则是通过内存或者其他高速存储来加速,但是由于用到了分布式环境中,涉及到并发和网络的问题,所以会更加复杂一些,但是有很多方面的共性,比如缓存淘汰策略。计算机行业有一句鼎鼎大名的格言就指出了缓存失效的复杂性。

There are only two hard things in Computer Science: cache invalidation and naming things (计算科学中最难的两件事是命名和缓存失效)
– Phil Karlton

本文包括四个部分,分布式缓存的更新模式、失效机制、淘汰策略和常见问题及解决方案,重点是围绕缓存的通用原理和实现来说明,不针对某个具体的系统,算法部分主要采用伪代码说明。

缓存的更新模式

Cache Aside模式

  1. 读取失效:cache数据没有命中,查询DB,成功后把数据写入缓存
  2. 读取命中:读取cache数据
  3. 更新:把数据更新到DB,失效缓存

图示

c5abf00e-c6ac-11e8-bd94-0242ac00da81.png

// Read
data = cache.get(id);
if (data == null) {data = db.get(id);cache.put(id, data);
}// Write
db.save(data);
cache.invalid(data.id);

为什么更新不直接写缓存?

为了降低并发情况下的数据不一致发生概率(cache aside无法完全避免数据不一致,只能降低发生的概率,如果需要数据强一致可以考虑使用分布式事务),如下图所示

Thread-A: Write DB Version(A)
Thread-B: Write DB Version(B)
Thread-B: Write Cache Version(B)
Thread-A: Write Cache Version(A) -- 数据库结果是B,缓存里面变成A了

这种情况下如果改为失效的话数据不一致的情况能够避免

Thread-A: Write DB Version(A)
Thread-B: Write DB Version(B)
Thread-B: Expire Cache
Thread-A: Expire Cache -- 两种情况只失效缓存,下次读操作会把db的最新值刷新到缓存中。

Read/Write Through模式

缓存代理了DB读取、写入的逻辑,可以把缓存看成唯一的存储。

f9681e7c-c6ac-11e8-976c-0242ac00da81.png

# [xxx] 表示一个组件
# 箭头表示数据方向
Read:
Client <-- [Cache <-- DB]Write:
Client --> [Cache --> DB]

Write Behind Caching(Write Back)模式

这种模式下所有的操作都走缓存,缓存里的数据再通过异步的方式同步到数据库里面。所以系统的写性能能够大大提升了。

09cd7474-c6ad-11e8-94db-0242ac00da81.png

缓存失效策略

一般而言,缓存系统中都会对缓存的对象设置一个超时时间,避免浪费相对比较稀缺的缓存资源。对于缓存时间的处理有两种,分别是主动失效和被动失效。

主动失效

主动失效是指系统有一个主动检查缓存是否失效的机制,比如通过定时任务或者单独的线程不断的去检查缓存队列中的对象是否失效,如果失效就把他们清除掉,避免浪费。主动失效的好处是能够避免内存的浪费,但是会占用额外的CPU时间。

被动失效

被动失效是通过访问缓存对象的时候才去检查缓存对象是否失效,这样的好处是系统占用的CPU时间更少,但是风险是长期不被访问的缓存对象不会被系统清除。

缓存淘汰策略

缓存淘汰,又称为缓存逐出(cache replacement algorithms或者cache replacement policies),是指在存储空间不足的情况下,缓存系统主动释放一些缓存对象获取更多的存储空间。

对于大部分内存型的分布式缓存(非持久化),淘汰策略优先于失效策略,一旦空间不足,缓存对象即使没有过期也会被释放。这里只是简单介绍一下,相关的资料都很多,一般LRU用的比较多,可以重点了解一下。

FIFO

先进先出(First In First Out)是一种简单的淘汰策略,缓存对象以队列的形式存在,如果空间不足,就释放队列头部的(先缓存)对象。一般用链表实现。

LRU

最近最久未使用(Least Recently Used),这种策略是根据访问的时间先后来进行淘汰的,如果空间不足,会释放最久没有访问的对象(上次访问时间最早的对象)。比较常见的是通过优先队列来实现。

LFU

最近最少使用(Least Frequently Used),这种策略根据最近访问的频率来进行淘汰,如果空间不足,会释放最近访问频率最低的对象。这个算法也是用优先队列实现的比较常见。

分布式缓存的常见问题

缓存穿透

  • DB中不存在数据,每次都穿过缓存查DB,造成DB的压力。一般是网络攻击
  • 解决方案:放入一个特殊对象(比如特定的无效对象,当然比较好的方式是使用包装对象)

代码示例

# 我们先看看最简单的青铜姿势
value = cache.get(key)
if value is None:value = db.get(key)# 由于value为空,实际上缓存并没有写进去,一旦这个key成为热点,db的压力将会极大cache.put(key, value, expire)return value
else:return value# 简单优化一下,升级成为白银姿势
wrapped_value = cache.get(key)
if wrapped_value is None:value = db.get(key)# 即使是空对象也通过包装对象放到缓存,当然考虑到空间还可以采用特殊值(比如-1代表不存在)的方式cache.put(key, wrapped_value(value), expire)return wrapped_value.value
else:return wrapped_value.value

缓存击穿

  • 在缓存失效的瞬间大量请求,造成DB的压力瞬间增大
  • 解决方案1:更新缓存时使用分布式锁锁住服务,防止请求穿透直达DB
  • 解决方案2:使用布隆过滤器(bloom filter)过滤,可以过滤掉大部分缓存中不存在的key,但是需要注意布隆过滤器并不能过滤所有不存在的key,用于大部分的业务场景足够了,但是有可能无法过滤网络攻击(通过构造出可以不被布隆过滤器过滤但是缓存中不存在的key)。
  • 当然,以上两种方案也可以结合起来使用。
# 白银姿势
wrapped_value = cache.get(key)
if wrapped_value is None:value = db.get(key)# 在写入缓存之前,大量的请求突然涌入,db瞬间被打垮cache.put(key, wrapped_value(value), expire)return wrapped_value.value
else:return wrapped_value.value# 在白银姿势的基础上我们再优化成黄金姿势
wrapped_value = cache.get(key)
if wrapped_value is None:# 查db之前加一把锁while wrapped_value is None:if try_lock(key):value = db.get(key)cache.put(key, wrapped_value(value), expire)return wrapped_value.valueelse:# 等待10毫秒之后重试sleep(0.01)wrapped_value = cache.get(key)return wrapped_value.value
else:return wrapped_value.value

缓存雪崩

  • 大量缓存设置了相同的失效时间,同一时间失效,造成服务瞬间性能急剧下降
  • 解决方案:缓存时间使用基本时间加上随机时间
# 通过随机失效时间登上王者姿势
wrapped_value = cache.get(key)
if wrapped_value is None:# 查db之前加一把锁while wrapped_value is None:if try_lock(key):value = db.get(key)# 嗯,就是一个随机失效时间,最好是在某个区间cache.put(key, wrapped_value(value), random_expire())return wrapped_value.valueelse:# 等待10毫秒之后重试sleep(0.01)wrapped_value = cache.get(key)return wrapped_value.value
else:return wrapped_value.value

参考资料

  • 缓存的三种方案

http://chatgpt.dhexx.cn/article/cLjllMS0.shtml

相关文章

分布式缓存

本文介绍关于缓存的常用设计模式。以及如何保证缓存的一致性进行分类讨论。 还会介绍关于缓存失效的常见问题&#xff0c;以及针对缓存失效的解决方法。 在高并发的环境下&#xff0c;比如春节抢票大战&#xff0c;一到放票的时间节点&#xff0c;分分钟大量用户以及黄牛的各种…

详解分布式系统的缓存设计

作者&#xff1a;vivo互联网服务器团队-Zhang Peng ​ 一、缓存简介 1.1 什么是缓存 缓存就是数据交换的缓冲区。缓存的本质是一个内存 Hash。缓存是一种利用空间换时间的设计&#xff0c;其目标就是更快、更近&#xff1a;极大的提高。 将数据写入/读取速度更快的存储&#xf…

今天带你了解-分布式缓存(一)

在网站架构的衍化历程中&#xff0c;当网站遇到性能瓶颈时&#xff0c;首先想到的解决方案就是使用缓存。 缓存指将数据存储在较高访问速度的存储介质中&#xff0c;以供系统处理。一方面缓存访问速度快&#xff0c;可以减少数据的访问时间&#xff0c;另一方面如果缓存的数据…

深入浅出分布式系统中的缓存架构

缓存&#xff0c;已经是一个老生常谈的技术了&#xff0c;在高并发读的情况下对于读服务来说可谓是抗流量的银弹。 高并发三大利器&#xff1a;缓存、限流、降级。 今天我们就来谈谈缓存。对于缓存&#xff0c;我的理解是让数据更接近于用户&#xff0c;目的是让用户的访问速…

分布式缓存灵魂十连,你能坚持几个?

点击上方蓝色“方志朋”&#xff0c;选择“设为星标” 回复“666”获取独家整理的学习资料&#xff01; 目录 前言 目前工作中用到的分布式缓存技术有redis和memcached两种&#xff0c;缓存的目的是为了在高并发系统中有效降低DB的压力&#xff0c;但是在使用的时候可能会因为缓…

Webform 常用控件

Webform 常用控件 一&#xff0c;简单控件 1&#xff0c;Lable——标签&#xff1a;在网页中呈现出来的时候会变成span标签 属性&#xff1a;Text——标签上的文字 BackColor&#xff0c;ForeColor——背景色&#xff0c;前景色 Font——字体 Bold-加粗 Italic-倾斜 Under…

Web窗体(WebForm)

一&#xff0e;简介 0. 页面的生命周期。 1. WebForm后台页面类继承于Page类&#xff0c;Page类实现了IHttpHandler接口。 2. 前台页面类继承于后台页面类。 3. 先调用PageLoad方法&#xff0c;再调用Render方法生成html代码。 二. 加密安全 互联网没有绝对的安全&#xff0c;登…

ASP.NET Web Form学习

ASP.NET Web Form学习 0.aspx与html 它如何工作&#xff1f; 从根本上讲&#xff0c;ASP.NET 页面与 HTML 完全相同。 HTML 页面的扩展名是 .htm 或 .html。假如浏览器从服务器请求某张 HTML 页面&#xff0c;服务器不进行任何修改&#xff0c;就会把该页面发往浏览器。 A…

forms.Form和forms.ModelForm

forms.ModelForm是forms.Form的升级版 forms.Form验证规则 2.1 forms.py 2.2 view.py 把我们写的UserResetForm导入到view.py 2.3 模板 forms.ModelForm验证规则 3.1 models.py 3.2 forms.py就用上面模型类里面的验证规则 3.3 view.py 3.4 模板看你实际的情况 forms.…

WebForm与MVC混用

在现有的WebForm项目中加入MVC&#xff0c;可以吗&#xff1f; 西蒙说&#xff0c;可以。 怎么加呢&#xff1f; 我的开发环境是&#xff1a;WIN7 IIS7.5 VS2012 一、WebForm项目添加引用&#xff1a; 我都是选了最高的版本。 二、将MVC项目的部分文件拷贝到WEBFORM项目 …

ASP.NET WebForm+Vue.js

QQ&#xff1a;285679784 欢迎加入博主CSDN资源QQ群799473954(附加信息&#xff1a;CSDN博客)一起学习 ! 参考原文&#xff1a;https://blog.csdn.net/myppbird/article/details/85598154 Vue.js教程&#xff1a;http://www.runoob.com/vue2/vue-tutorial.html Vue.js Ajax…

解析ASP.NET WebForm和Mvc开发的区别

因为以前主要是做WebFrom开发,对MVC开发并没有太深入的了解。自从来到创新工场的新团队后,用的技术都是自己以前没有接触过的,比如:MVC 和EF还有就是WCF,压力一直很大。在很多问题都是不清楚的情况下,问周围的人,别人也只是给自己讲一个大概。而且前两天因为问了一个比较…

C#-WebForm-WebForm开发基础

1、C/S  客户端应用程序 WinForm  WPF  平级 数据是存放在其他的电脑上或服务器上 需要从服务器上下载相应的数据&#xff0c;在本地电脑上的客户端里进行加工 数据的加工是在用户的电脑上执行的&#xff0c;会对用户的电脑配置有所要求 2、B/S  网页端应用程序 ASP.NE…

ASP.NET--WebForm框架

WebForm框架 WebForm是微软推出的一款为了吸引更多的其他开发者能够快速入门到.NET技术中的一个框架,让开发人员感觉使用.NET技术进行BS开发和使用.NET技术进行CS开发一样简单快速。 微软的想法:以后程序员开发网站项目不需要先学习HTML+CSS就能够直接入门BS,因此将HTML标…

ASP.NET(二)--WebForm框架

WebForm框架 WebForm框架WebForm介绍WebForm窗体框架事件驱动开发简单应用ASP.NET程序开发过程网页生成过程的分析案例 WebForm框架 WebForm是微软推出的一款为了吸引更多的其他开发者能够快速入门到.NET技术中的一个框架&#xff0c;让开发人员感觉使用.NET技术进行BS开发和使…

winform webform 简单高效的UI界面框架

一、winform的界面框架设计&#xff08; james_lx&#xff09; 一直以来&#xff0c;我都在寻找WINFORM程序&#xff0c;简单高效的UI界面框架&#xff0c;终于&#xff0c;我领悟了。在此分享给和我一样使用C#而苦于界面设计的人。我的发现中&#xff0c;并没有加入什么其它的…

WebForm 基础学习

C/S 客户端应用程序&#xff08;Client/Server&#xff09; 客户端——服务器端 两种技术 WinForm WPF -- WPF开发于WinForm之后&#xff0c; --GUI 图形用户界面&#xff08;采用图形方式显示的计…

c#webform制作网页应用的思路login登录

一方面是有实际工作需要&#xff0c;一方面是想学点东西&#xff0c;制作一个webform的信息科工单登记查询网页&#xff0c;虽然目前还是很多bug&#xff0c;虽然代码基本都是复制的&#xff0c;但是慢慢的搭建成型。自己记录下思路&#xff0c;纯小白&#xff0c;可以说基本都…

<form>表单

1.form表单 <form>标签是表单是一个框架&#xff0c;其中主要包含<input>,<textarea>,<select>标签 1.1<input>标签 <input>标签首先是一个单标签 <input>标签的格式是<input type"" value"" name"&…

表单<form>

创建表单 <form> 标签用于创建供用户输入的 HTML 表单。 <form>标签的action属性的值指定了表单提交到服务器的地址。 <form> 元素包含一个或多个如下的表单元素&#xff1a; <input> <textarea> <button> <select> <option&g…