全栈工程师修炼指南

赫赫有名的双刃剑:缓存(下)

2020-11-24  本文已影响0人  码农架构

缓存使用的问题

缓存穿透 (没起作用)

缓存穿透,指的是在某些情况下,大量对于同一个数据的访问,经过了缓存屏障,但是缓存却未能起到应有的保护作用

举例来说,对某一个 key 的查询,如果数据库里没有这个数据,那么缓存中也没有数据的存放,每次请求到来都会去查询数据库,缓存根本起不到应有的作用

解决:

特殊情况:

一般的缓存策略下,往往需要先发生一次缓存命中失败,接着从实际存储(比如数据库)中得到结果,再回填到内存缓存中。但是,如果这个数据库查询过程比较慢,大量同一数据的请求像雨点一样几乎同时到来,就会全部穿透缓存,一并落到了数据库上,而那个时候最早的那个请求引发的缓存回填甚至都还没有发生,在这种情况下数据库直接就挂掉了,虽然缓存的机制本身看起来并没有任何问题。

解决:

缓存雪崩 (崩了)

原本起屏障作用的缓存,如果在一定的时间段内,对于大量的请求访问失效,即失去了屏障作用,造成它后方的系统压力过大,引起系统过载、宕机等问题,就叫做缓存雪崩。

解决:

其它场景:

缓存数据通常都有过期时间的,如果缓存加载的时间比较集中,那么很可能到了某一时间点,大量的缓存就会同时过期,于是对应这些数据的请求全部落到了后面的数据库上,从而造成系统崩溃。

解决:

缓存容量失控

可能的原因:

LRU 的致命缺陷

LRU 指的是 Least Recently Used,最少最近使用算法。这是缓存队列维护的最常见算法,原理是:维护一个限定最大容量的队列,队列头部总是放置最近访问的元素(包括新加入的元素),而在超过容量限制时总是从队尾淘汰元素。

image.png

如果用户有意无意地访问一些错误信息,就会破坏掉这个 LRU 队列中最近访问数据的真实性。

解决:

缓存框架

鉴于缓存的普遍性,缓存框架也可以说是百花齐放。

集成方式

方式 1:编程方式

Cache<String, City> cityCache = cacheManager.createCache("cityCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, City.class, resourcePools));
cityCache.put("Beijing", beijingInfo); 
City beijing = cityCache.get("Beijing"); 

方式 2:方法注解

@Cacheable(value="getCity", key="#name")
public City getCity(String name) { ... }

方式 3:配置文件的注入

<mapper namespace="..." >
  <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  ...
</mapper>

方式 4:Web 容器的 Filter

在 Ehcache 2 中,可以配置 net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter 这样一个 filter 到 Tomcat 的 web.xml 中,再配合 filter 的映射匹配参数和初始化参数,就可以实现整个请求的过滤功能。

方式 5:页面模板中的 Cache 标签

这种方式相对比较少见,有一些页面模板支持 Cache 标签或表达式语法(例如 Django 中,它被称为 Template Fragment Caching),在标签属性或语法参数中可以指定缓存的时间和条件,标签内部的 HTML 将被缓存起来,以避免在每次模板渲染时都去执行其中的逻辑。

核心要素

要素 1:缓存数据的生命周期管理

<mapper namespace="..." >
  <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  ...
</mapper>
  1. Flush,右侧黄色的箭头,数据从高层向低层移动;
  2. Fault,左侧绿色箭头,数据从低层拷贝到高层,但不删除;
  3. Eviction,下方红色箭头,数据永久淘汰出缓存数据容器;
  4. Expiration,上方烟灰色图案,数据过期了,意味着可以被 flushed 或者 evicted,但是考虑到性能,不一定立即执行这个操作;
  5. Pinning,右上角蓝色图案,数据被强制钉在某一层,不受流动规则控制。

要素 2:数据变动规则

要素 3:核心 API

这里本质上反映的是缓存框架实现的时候,核心代码结构的设计。当我们把这类的代码结构设计进一步上升到规范层面,它们就可以被定义成接口,即允许不同的缓存框架可以实现同样的设计.

要素 4:用户侧 API

这是指暴露给用户访问缓存的接口,比如常见的向缓存内放置一条数据的接口,或者从缓存内取出一条数据的接口。值得一提的是,我们通常见到的用户 API 都是 Map-like 的结构,即众所周知的 key-value 形式,但其实缓存框架完全可以支持其它的形式,这取决于数据访问的方式,因此这并不是一个绝对的限制。


公众号:码农架构
上一篇下一篇

猜你喜欢

热点阅读