架构系列四(缓存方案架构设计思考)
Posted 地道程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了架构系列四(缓存方案架构设计思考)相关的知识,希望对你有一定的参考价值。
1.引子
我们都知道,在一个应用系统中,存储层非常重要,它负责整个应用系统数据的持久化,比如关系型数据库oracle、mysql是很多业务系统存储层选型的不二之选。
另外我们还知道,存储层往往容易成为系统性能的瓶颈,持久化需要经过文件系统,并进一步读写磁盘,从系统的角度,IO操作是成本很高的一个事。
因此很多时候,我们在设计系统架构方案的时候,会结合业务考量,在应用层,与存储层之间增加一个中间层:缓存层。用于提升应用的响应能力,并有效降低存储层的负载。我们来看一个对比图:
从图上我们看到,左边是无缓存架构方案,右边是增加缓存层架构方案
通过缓存层,应用层会优先访问缓存层获取数据,如果缓存层获取到目标数据,从缓存层直接返回
如果缓存层未命中目标数据,则访问存储层获取数据。获取到数据后,从存储层返回,且将数据写入缓存层
这里你可能在想,为什么增加了一层缓存层,架构复杂度不是更高了吗?怎么就性能更高了呢?这是因为缓存层操作的是内存,存储层是磁盘,访问内存的性能远远高于访问磁盘
增加缓存层,提升了应用的响应能力,并有效降低后端存储层负载。但是在缓存架构设计方案中,需要我们考虑的地方还是比较多的,有
哪些业务数据适合缓存架构方案
缓存粒度
缓存一致性
缓存穿透
缓存雪崩
缓存热点key
下面我们一起逐个来分析,让我们开始吧!
2.案例
2.1.哪些业务数据适合缓存
虽然缓存纵有千般好,我们切不可拿来主义,一定需要结合业务自身的需要。那么哪些数据适合缓存方案呢?
不适合缓存方案:实时性要求高的业务数据,不适合采用缓存方案。比如说交易类数据订单、支付
适合集中式缓存:更新频率不高,读多写少类业务数据,适合采用集中式缓存方案。比如说用户数据、商品数据
适合本地缓存:非常稳定类业务数据,且数据量级不大,适合采用本地缓存方案。比如说配置、参数类数据
你看区分业务数据是否适合采用缓存方案,我们主要有两个考虑的点
实时性:实时性要求高的业务数据,不适合缓存;反之则适合缓存方案
数据量级:在采用缓存方案的时候,如果数据非常稳定,且数据量级不大,我们还可以考虑直接使用本地缓存方案。通过本地缓存减少网络调用,进一步提升应用响应能力
2.2.缓存粒度
关于缓存粒度,我们知道增加缓存层方案,本质上是通过空间换取时间的方案。
即牺牲更多的存储空间,从而提升应用的响应能力。因此我们在设计缓存方案的时候,需要结合业务需要,处理好缓存粒度,避免过渡浪费存储空间。举个例子
不要把select * from xxx的数据都放入缓存
而是结合业务需要,明确业务字段select a,b,c from xxx放入缓存
2.3.缓存一致性
缓存一致性问题,是缓存架构方案中非常重要,又需要慎重的一个问题。这里说的一致性问题,即存储层、缓存层数据的一致性问题,涉及到缓存的更新。缓存更新实际应用中可以结合缓存淘汰、超时过期、主动更新实现。
当然我们在系统中使用缓存架构方案的时候,通常业务上可以允许一定时间内的数据不一致,但这不是说可以放纵数据不一致。那么当存储数据更新以后,如何处理缓存层的数据,从而保持一致呢?
结合业务需求,我们可以去思考这么几个点,以下是主动更新的策略
更新策略:更新存储层,更新缓存层;或者更新缓存层,更新存储层
删除策略:更新存储层,删除缓存层;或者删除缓存层,更新存储层
你看这里有两种策略可以选择,具体哪一种更优呢?首先我们来看更新策略,这是一种不推荐的方案,理由是更新成本往往更高
也许放入缓存中的数据,需要经过一定的计算,计算成本高
也许连续更新多次,且这段时间没有读请求,事实上缓存中需要存储最近一次更新的结果,那么前n-1次更新都是浪费
分析了更新策略不是推荐方案后,我们再来看删除策略,这是实际应用中推荐的方案,理由是删除策略比起更新策略,陈本更低
删除动作,比更新动作更轻,没有计算成本
连续多次更新存储层,缓存层都是一个轻量级的删除操作,不存在浪费n-1次更新的问题
缓存层删除后,等到接收到读请求,再从存储层获取数据--->计算--->放入缓存层
2.4.缓存穿透
关于缓存穿透,首先我们需要明确什么是缓存穿透?我们说当应用缓存架构方案以后,获取数据的流程是这样的:应用层--->缓存层--->存储层。
当发生这样的情况,请求从应用层打进来,从缓存层获取不到目标数据,从存储层也获取不到数据。导致每次请求都直接打到了存储层,从而失去了缓存层降低存储负载的目的,更有甚者存储压力过大,最终系统崩溃!尤其是遭遇恶意攻击就更加麻烦了!
因此在缓存加过方案中,缓存穿透是一个大问题。那么有什么方案可以解决缓存穿透吗?有
存储空值策略:存储空值方案,具体是说如果请求从缓存层没有获取到数据,进一步从存储也没有获取到数据。这个时候我们可以存储一个null值到缓存中,那么下一次同样的请求只需从缓存存直接返回null值即可,避免将请求再次打到存储层。
布隆过滤器策略:如果业务需要缓存的数据,有明确的业务边界,那么我们可以在缓存层前,增加一个布隆过滤器过滤处理所有的业务key,避免非法的业务请求,尤其是恶意攻击。
2.5.缓存雪崩
缓存雪崩,是指当缓存系统不可用,请求都直接打到存储层,从而存储层因为压力过大产生故障。
可见缓存雪崩有两个特点
主要原因是缓存系统不可用
进一步导致存储层崩溃
因此,在设计缓存架构方案的时候,为了避免缓存雪崩的发生,缓存系统需要考虑
高可用
容错降级
2.6.缓存热点key
所谓热点key,是指这么一种场景。由于存在缓存过期的问题,比如说某个业务数据key触发了超时过期,需要重建缓存。
正常情况下
一个读请求过来
发现从缓存层拿不到数据,则从存储层获取数据
将数据放入缓存层
这没有什么问题。但是要是恰巧
多个读请求同时过来,并发问题
大家发现从缓存层拿不到数据,就都从存储层获取数据
获取到数据后,都将数据放入缓存层
这有什么问题呢?这会导致并发请求直接打到存储层,存储层压力过大;另外多个并发读请求重复处理缓存重建,浪费资源,极端情况下可能还会发生死锁。
因此缓存架构方案中,热点key的问题我们也不得不去考虑。实际应用中,该如何实现呢?我们来看一段伪代码
public <T> T get(String key){
T t = cache.get(key);
if(t == null){
if(lock.tryLock()){
// 1.从存储层获取数据
// 2.将数据放入缓存层
// 3.释放锁
}else{
// 1.休眠等待sleep(50)
// 2.再次调用get方法获取缓存
get(key);
}
}
return t;
}
以上是关于架构系列四(缓存方案架构设计思考)的主要内容,如果未能解决你的问题,请参考以下文章