引入缓存竟然会带来这么多问题?

哈喽,大家好呀,我是呼噜噜,最近很忙好久没更新了,今天我们通过缓存与数据库之间的一致性这个老生常谈的问题来切入,聊聊如何合理的设计一个缓存系统?
如今互联网应用 , 无论是web还是App,都基本遵循"前端-后端-数据库"的架构模型

引入缓存竟然会带来这么多问题?

文章插图
当业务处于起步阶段 , 流量比较小的时候,上述能够支撑;但随着业务的扩张,用户数和流量越来越大,也就需要整个架构支撑起更大的并发量,但我们服务器上的资源总是有限的,当每天流量达到高峰时 , 往往这个时候数据库最先顶不住
当我们分析这些互联网应用的流量时候,发现大部分的流量实际上都是读请求,而且大部分数据并没有频繁被改变**(即读多写少场景 , 注意本文全文讨论的方案都是基于这个前提**) 。这个时候引入缓存,是提升性能的一种行之有效的方式,缓存在计算机的世界中处处可见,比如CPU缓存,浏览器缓存 , 操作系统缓存,程序代码中自定义缓存
由于数据库每秒能接受的请求次数QPS是有限的,当我们在数据库前面,引入缓存来充当缓冲层;如果命中缓存就直接获取目标数据并返回,不仅能减少对数据库的直接访问带来的计算压力,还能提升响应速度 , 充分压榨有效的资源,其本质是额外消耗更高速的空间来换时间
引入缓存竟然会带来这么多问题?

文章插图
图片
凡是有利有弊,引入缓存后,享受缓存带来的种种好处的优点,但缓存系统其实是非常复杂的,缓存和数据库的一致性也是个绕不开,让人脑阔疼的问题;还需要考虑缓存的稳定性、命中率、热点数据、过期时间等等 , 我们下文慢慢道来
本地缓存、分布式缓存缓存有各种分类 , 常见的是与应用耦合程度划分为:本地缓存local cache和分布式缓存remote cache
本地缓存本地缓存,由于存在于应用程序的本地内存,应用和缓存在同一个进程内,且没有网络延迟,所以速度快
但本地缓存的大小通常受到物理内存的限制,而且还要兼顾应用程序正常运行,容量有限 , 扩展性差,无法轻松扩展到多个节点 。还有就是多个应用实例下无法直接的共享缓存,数据的一致性难以保证,复杂度高 。数据会随着应用程序的重启而丢失
适合读写密集、对数据一致性要求较低、网络环境不稳定的场景
分布式缓存主要是指与应用分离的独立缓存组件 , 比如redis,可扩展性强,容量大,可以通过集群水平扩展;通过通过一致性哈希等技术,保证多节点之间的数据一致性,而且都集成好了 , 开发者一般直接使用这些特性
当然由于存在网络延迟 , 与本地缓存相比,速度较慢;硬件成本也需要较高,来保证其高可用、高可靠性
更适合电商平台、社交网络等流量并发大的平台,或者互联网这种随着业务增长,需要弹性扩展以满足需求的场景
还有综合二者特点的多级缓存,将本地缓存和分布式缓存结合起来,本地缓存作为一级缓存,存储更新频率低,访问频率高数据;分布式缓存作为二级缓存,存储更新频率很高的数据
当用户获取数据时,先从一级缓存中获取数据,如果一级缓存有数据则返回数据,否则从二级缓存中获取数据 。如果二级缓存中有数据则更新一级缓存,然后将数据返回客户端 。如果二级缓存没有数据则去数据库查询数据,然后更新二级缓存,接着再更新一级缓存,最后将数据返回给客户端 。这里逻辑其实和CPU内部的缓存很像,大家感兴趣地可以自行查阅笔者之前的一篇文章-CPU缓存
但缓存相关的问题逻辑挑战,无论本地缓存还是分布式缓存都是一样的,为方便起见,本文将全文以redis为例,来代称缓存
缓存穿透、缓存击穿、缓存雪崩在将缓存和数据库的一致性之前,我们需要保证,引入的缓存,即构建的缓存系统是稳定的,这是保证数据一致性的前提
关于缓存的稳定性,有3种经典问题:缓存穿透、缓存击穿、缓存雪崩,聊这3个问题前,我们得知晓缓存最常见的应用模式Cache-Aside Pattern旁路缓存的读模式:
引入缓存竟然会带来这么多问题?

文章插图
图片
旁路缓存模式,是指优先查询缓存,查询不到再去查询数据库 。如果这时候数据库查到数据了,就将缓存的数据回写更新,这样缓存可以为后续请求服务!


推荐阅读