一、缓存
一般来说,现在的互联网应用、网站或者App的整体流程可以用下面这张图来表示:
用户请求,从界面,到网络转发,到应用服务,最后到存储(数据库或文件系统),然后再返回到界面呈现内容。
随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大。应用需要支撑更多的并发量。
同时,应用服务器和数据库服务器所做的计算也越来越多。
但是,往往应用服务器的资源是有限的。而且,技术变革是缓慢的。
数据库每秒能接收的请求次数也是有限的,或者说,文件的读写也是有限的。
如何能有效地利用有限的资源来提供尽可能大的吞吐量?
一个有效的办法就是引入缓存,打破我们标准的流程。
每个环节中,请求可以从缓存中直接获取目标数据并返回,从而减少它们的计算量,来有效提升响应速度。让有限的资源服务更多的用户。
像上图里展示的,缓存的使用其实可以出现在1~4的各个环节中,每个环节的缓存方案都各有特点。
二、缓存的特征
1、命中率:命中数/(命中数+未命中数)
①命中:直接通过缓存获取到需要的数据。
②不命中:无法通过缓存获取到需要的数据,需要再次查询数据库,或者执行其它的操作。原因可能是,由于缓存中根本不存在,或者缓存已经过期了。
缓存的命中率越高,它就表示我们使用缓存的收益越高,应用的性能越好
这个时候,应用的响应时间会变得更短,吞吐量会变得更高,抗并发能力也会变得更强。
因此,在高并发的互联网系统中,缓存的命中率,是至关重要的一个指标。
2、最大元素(最大空间)
它代表的是:缓存中可以存放的最大元素的数量
一旦缓存中,元素数量超过这个值,是指:缓存的数据所占的空间超过了最大支持的空间,将会触发缓存清空策略
根据不同的场景,合理地设计最大元素值,往往可以一定程度上,提高缓存的命中率,从而更有效地使用缓存
缓存的存储空间是有限制的,当缓存空间被用满时,如何保证在稳定服务的同时,有效地提升命中率?
这就由缓存的清空策略来处理
适合自身数据特征的清空策略能有效地提高命中率
3、常见的清空策略
①FIFO(first in first out):先进先出策略
它是指:最先进入缓存的数据,在缓存空间不够的情况下,或者是超出最大元素限制的时候,会优先被清除掉,以腾出新的空间,来接受新的数据
这个策略算法,主要是比较缓存元素的创建时间
在数据实时性要求场景下,可以选择该类策略,优先保障最新数据可用
②LFU(least frequently used):最少使用策略
它是指:无论是否过期,根据元素的被使用次数来判断
清除使用次数最少的元素,来释放空间
这个策略的算法,它主要比较元素的命中次数
在保证高频数据有效性场景下,可以选择这种策略
③LRU(Least Recently Used):最近最少使用策略
它是指:无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素,释放空间
这个策略算法,主要比较元素的最近一次被GET使用时间
在热点数据场景下,较适用,优先保证热点数据的有效性
④过期时间
根据过期时间判断,清理过期时间最长的元素
或者
根据过期时间判断,清理最近要过期的元素
⑤随机
随机清理
三、影响缓存命中率的因素
1、业务场景和业务需求
①缓存通常适合读多写少的业务场景
反之,使用缓存的意义其实并不大,命中率会很低
②业务需求也决定了对实时性的要求,直接影响到缓存的过期时间和更新策略
实时性要求越低,就越适合缓存
在相同key和相同请求数的情况下,缓存的时间越长,命中率就会越高
我们目前遇到的互联网应用,大多数的业务场景下,都是很适合使用缓存的
2、缓存的设计(粒度和策略方面)
通常情况下,缓存的粒度越小,命中率就会越高
实际的例子:
①当缓存单个对象的时候,比如单个用户信息
只有当该对象的对应的数据,发生变化的时候
我们才需要更新缓存或者移除缓存
②当缓存一个集合的时候
例如,我们要缓存所有用户数据
其中任何一个对象对应的数据发生变化时
我们都需要更新或者移除缓存
③还有另一种情况,假设其它地方,也需要获取该对象对应的数据时
比如说,其它地方也需要获取单个用户信息
如果缓存的是单个对象,那么就可以直接命中缓存
否则的话,就无法直接命中
这样的话,会更加灵活,缓存的命中率会更高
④此外,缓存的更新、过期策略也直接影响到缓存的命中率
当数据发生变化时,直接更新缓存的值,会比移除缓存,或者让缓存过期,它的命中率更高
当然,这个时候的系统复杂度,也会变得更高
3、缓存容量和基础设施
①缓存的容量有限,它就容易引起缓存失效和被淘汰
目前,多数的缓存框架或中间件,都采用了LRU算法
②同时,缓存的技术选型也是至关重要的
比如:采用应用内置的本地缓存,就比较容易出现单机瓶颈
而采用分布式缓存,就更容易扩展
所以,需要做好系统容量规划,并考虑是否可扩展
③此外,不同的缓存框架或中间件,它们的效率和稳定性,也是存在一些差异的
④除此之外,还有一些其它的因素,会影响到缓存的命中率
比如:当缓存节点发生故障的时候,需要避免缓存失效,并最大程度地降低影响
业内比较典型的做法,就是通过一致性哈希算法或者节点冗余的方式来避免这个问题
⑤理解误区
既然业务需求对数据实时性要求很高,而缓存时间又会影响到缓存命中率,那么,系统就别使用缓存了
其实,非常容易忽略一个重要的因素,就是并发
通常来讲,在相同缓存时间和key的情况下,并发越高,缓存的收益就会越高,即使缓存的时间很短
四、如何提高缓存命中率
需要应用尽可能地通过缓存来直接获取数据,并避免缓存失效
这也是比较考验能力的,需要在业务需求,缓存粒度,缓存策略,技术选型等各个方面,去通盘考虑,并做权衡
尽可能地聚焦在高频访问,且时效性要求不高的热点业务上
通过缓存预加载(预热),增加存储容量,调整缓存粒度,更新缓存等手段来提高命中率
对于时效性很高,或者是缓存空间有限的情况下:内容跨度越大,或者访问很随机,并且访问量不高的应用来说
缓存命中率可能会长期都很低
可能预热后的缓存,还没来得及被访问,就已经过期了
五、缓存分类和应用场景
目前的应用服务框架中,比较常见的,是根据缓存与应用的耦合度,分为本地缓存和分布式缓存
1、本地缓存指的是:应用中的缓存组件
它最大的优点是:应用和Cache是在同一个进程的内部,请求缓存非常得快速,没有过多的网络开销
在单机应用中,不需要集群支持或者集群情况下,各节点无需互相通知的场景下,使用本地缓存比较合适
它的缺点是:缓存跟应用程序耦合,多个应用程序无法直接共享缓存
各应用或集群的各个节点,都需要维护自己单独的缓存,有时对内存也是一种浪费
我们涉及到本地缓存,通常一般是编程实现,使用成员变量,局部变量,静态变量等来缓存数据
当然,也有提供好的现成的框架,比如Guava Cache、Ehcache
2、分布式缓存
它指的是:应用分离的缓存组件或服务
它最大的优点是:自身就是一个独立的应用,与本地应用是隔离的,多个应用可以直接共享缓存
比如Redis、Memcache,它们都可以用来作为分布式缓存