最近在看一本讲数据库架构的英文书,书中很多次提及到一个叫缓存的词语,在我们商城的业务系统中也经常听到缓存这个词语。于是百度找到这篇文章。内心觉得总结很到位。转自:缓存技术原理

一、前言

应用中使用缓存技术,往往可以大大减少计算量,有效提升响应速度,让有限的资源服务更多的用户。但是,似乎还没有一种缓存方案可以满足所有的业务场景,我们需要根据自身的特殊场景和背景,选择最适合的缓存方案,尽量以最小的成本最快的效率达到最优的目的。本文将从多个方面对缓存进行分析,以便作为选择缓存方案的考量。
二、文章要点

三、缓存的理解 3.1 狭义的理解

缓存指的是 CPU 缓存,当 CPU 要读取一个数据时,首先从 CPU 缓存中查找,找到就立即读取并送给 CPU 处理;没有找到,就从速率相对较慢的内存中读取并送给 CPU 处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。

3.2 广义的理解

凡是位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为缓存。

3.3 缓存的优点

如下,一个 Web 应用架构一般有如下几层:

在此架构的不同层级之间,都可以存在缓存。比如:

总结来说,缓存在如下三个方面做了提升:

四、CPU 缓存简介

CPU 缓存(Cache Memory)是位于 CPU与 内存之间的临时存储器,它的容量比内存小的多,但是交换速率却比内存要快得多。缓存的出现主要是为了解决 CPU 运算速率与内存读写速率不匹配的矛盾,因为 CPU 运算速率要比内存读写速率快很多,这样会使 CPU 花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内 CPU 即将访问的,当 CPU 调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速率。由此可见,在 CPU 中加入缓存是一种高效的解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速率,又有内存的大容量的存储系统了。 缓存基本上都是采用 SRAM 存储器,存储器在计算机内部的组织方式如下图所示:

越往上,存储器的容量越小、成本越高、速度越快。由于 CPU 和主存之间巨大的速度差异,系统设计者被迫在 CPU 寄存器和主存之间插入了一个小的 SRAM 高速缓存存储器称为 L1 缓存,大约可以在 2-4 个时钟周期(计算机中最小的时间单位)内访问。再后来发现 L1 高速缓存和主存之间还是有较大差距,又在 L1 高速缓存和主存之间插入了 L2 缓存,大约可以在 10 个时钟周期内访问。后面还新增了 L3 等,于是,在这样的模式下,在不断的演变中形成了现在的存储体系。
五、分布式缓存原理 5.1 本地缓存

本地缓存可能是大家用的最多的一种缓存方式了,如 Ehcache、Guava Cache 等,它是在应用中的缓存组件,其最大的优点是应用和 cache 是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适; 同时,它的缺点也是因为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。

5.2 分布式缓存特性

分布式缓存能够高性能地读取数据、能够动态地扩展缓存节点、能够自动发现和切换故障节点、能够自动均衡数据分区,而且能够为使用者提供图形化的管理界面,部署和维护都十分方便。优秀的分布式缓存系统有 Memcached、Redis,还有阿里自主开发的 Tair 等;

那么,分布式缓存又是如何做的呢?
5.3 分布式缓存实现原理

数据读取

分布式缓存由一个服务端实现管理和控制,由多个客户端节点存储数据,以达到提高数据的读取速率。那读取某个数据的时候,可以根据一致性哈希算法确定数据的存储和读取节点。以数据 D,节点总个数 N 为基础,通过一致性哈希算法计算出数据 D 对应的哈希值(相当于门牌号),根据这个哈希值就可以找到对应的节点了。一致哈希算法的好处在于节点个数发生变化(减少或增加)时无需重新计算哈希值,保证数据储存或读取时可以正确、快速地找到对应的节点。

数据均匀分布

由多个客户端节点存储数据时,需要保证数据均匀分布。比如,服务器数量较少,很可能造成有些服务器存储的数据较多,承担的压力较大,有些服务器就比较空闲。 解决的办法就是,把一台服务器虚拟成多台服务器,可以在计算服务器对应的哈希值时,在IP地址字符串加多个“尾缀”,比如:10.0.0.1/#1 10.0.0.1/#2 10.0.0.1/#3… 这样,一台物理服务器就被虚拟化成多台服务器。

数据的热备份

实现数据的热备份之前,需要了解一致性哈希算法,计算多台服务器的 IP 地址哈希值时,是将这些哈希值从小到大按顺时针排序组成一个“服务器节点环”。以顺时针方向看“服务器环”,当有客户端把数据存储在第1台服务器上后,第1台服务器负责把该数据拷贝一份给第 2 台服务器,以此类推,也就是说“服务器环”上的每一个节点,都是上一个节点的热备份节点。同时,一个服务器上存了两类数据,一类是自身的业务数据,一类是上一节点的热备数据。
六、影响缓存性能因素 6.1 序列化

访问本地缓存,对于 JVM 语言,有堆内和堆外缓存可以进行选择。由于对内直接以对象的形式进行存储,不需要考虑序列化,而堆外是以字节类型进行存储,就需要进行序列化和反序列化。 序列化一般需要解析对象的结构,而解析对象结构,会带来较大的 CPU 消耗,所以一般的序列化(比如 fastJson)均会缓存对象解析的对象结构,来减少 CPU 的消耗。 具体序列化性能对比这里就不做罗列,可点击 link 这里查看。

6.2 命中率

通常来讲,缓存的命中率越高则表示使用缓存的收益越高,应用的性能越好(响应时间越短、吞吐量越高),抗并发的能力越强。那么影响缓存命中率因素有哪些呢?

业务场景和业务需求

缓存的设计粒度和策略

(1)固定过期时间,被动失效;

(2)感知数据变更,主动更新;

(3)感知数据变更,主动更新。并设置过期时间被动失效兜底;

(4)按照数据冷热性制定策略,如热数据主动失效并 reload,冷数据只失效不 reload 等。

然而,当数据发生变化时,直接更新缓存的值会比移除缓存(或者让缓存过期)的命中率更高,当然,系统复杂度也会更高。

缓存容量和基础设施

缓存的容量有限,则容易引起缓存失效和被淘汰(目前多数的缓存框架或中间件都采用了 LRU 算法)。同时,缓存的技术选型也是至关重要的,比如采用应用内置的本地缓存就比较容易出现单机瓶颈,而采用分布式缓存则比较容易扩展。所以需要做好系统容量规划,并考虑是否可扩展。此外,不同的缓存框架或中间件,其效率和稳定性也是存在差异的。

其他因素

缓存故障处理:当缓存节点发生故障时,需要避免缓存失效并最大程度降低影响,业内比较典型的做法就是通过一致性 Hash 算法,或者通过节点冗余的方式。

以上可见,想要提高缓存收益,需要应用尽可能的通过缓存直接获取数据,并避免缓存失效。需要在业务需求,缓存粒度,缓存策略,技术选型等各个方面去通盘考虑并做权衡。尽可能的聚焦在高频访问且时效性要求不高的热点业务上,通过缓存预加载(预热)、增加存储容量、调整缓存粒度、更新缓存等手段来提高命中率。
6.3 缓存清空策略

通过前面介绍,我们知道缓存策略对于缓存的性能具有很大的影响。那么,缓存策略是为了解决什么问题,又有哪些方案可选呢?

面临的问题

主存容量远大于 CPU 缓存,磁盘容量远大于主存,因此无论是哪一层次的缓存都面临一个同样的问题:当容量有限的缓存的空闲空间全部用完后,又有新的内容需要添加进缓存时,如何挑选并舍弃原有的部分内容,从而腾出空间放入这些新的内容。

解决方案

解决这个问题的算法有几种,如最久未使用算法(LRU)、先进先出算法(FIFO)、最近最少使用算法(LFU)、非最近使用算法(NMRU)等,这些算法在不同层次的缓存上执行时拥有不同的效率和代价,需根据具体场合选择最合适的一种。下面针对每一种算法做一个简单介绍:

七、高并发场景常见缓存问题

通常来讲,在相同缓存时间和 key 的情况下,并发越高,缓存的收益会越高,即便缓存时间很短。而高并发应用场景下一般会引发以下常见的三个问题。

7.1 缓存穿透问题

问题描述

出现场景:指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能 DB 就挂掉了。要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案

7.2 缓存并发问题

问题描述

有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询 DB,同时设置缓存的情况,如果并发确实很大,这也可能造成 DB 压力过大,还有缓存频繁更新的问题。

解决方案

可以对缓存查询加锁,如果 KEY 不存在,就加锁,然后查 DB 入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入 DB 查询。
7.3 缓存失效问题

问题描述

引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置 1 分钟啊,5 分钟这些,并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发一当过期时间到后,这些缓存同时失效,请求全部转发到 DB,DB 可能会压力过重。

解决方案

其中的一个简单方案就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
总结

到这里,关于缓存的内容就介绍完毕了,相信通过本文可以帮助我们理解缓存的基本工作原理,了解常见缓存问题的解决思路。