缓存技术内部交流_04_Cache Aside续篇
额外参考资料:
http://www.ehcache.org/documentation/3.2/expiry.html
F. Cache Aside 模式的问题:缓存过期
有时我们会在上线前给缓存系统来个预热,提前读取一部分用户信息到缓存中。默认情况下,这些缓存项拥有相同的 ttl 设置,会在一个很短的时间段内大批量的过期,导致这段时间后端 SoR 压力过大,可能会导致整个系统崩溃。
如果我们给每个缓存项设计一个随机的过期时间,就可以避免缓存过期的集中爆发。
G. Cache Aside 模式的问题:缓存穿透
当查询无结果时,一般情况下我们不会缓存这次查询(Ehcache3 也不允许缓存 null 值)。但是,有时由于程序缺陷或者恶意攻击,短时间内会有大量异常查询请求到达系统,这些请求全都会透过缓存层到达 SoR,可能导致后端 SoR 崩溃。这就是缓存穿透。
因此,对于查询结果为 null 的请求,我们也需要缓存起来。只不过缓存的过期时间必须很短,防止恶意攻击程序制造出太多无用的缓存项,把整个缓存无效化。
缓存穿透的另一种解决办法是使用布隆过滤器将不存在的 id 提前拦截掉,降低 SoR 层的压力。布隆过滤器不在本次交流范围内,具体细节可以参考以下链接:
https://mp.weixin.qq.com/s/TBCEwLVAXdsTszRVpXhVug
http://blog.csdn.net/dadoneo/article/details/6847481
https://en.wikipedia.org/wiki/Bloom_filter
H. Ehcache3 自定义过期策略
通过实现 Expiry 接口即可以自定义过期策略。
public interface Expiry<K, V> {
/**
* 当缓存项第一次写入缓存中时,为该缓存项设置过期时间(从当前时间开始,超过指定的 Duration 时间即为过期)。
* 返回值不可以为 null。
* 当该方法抛出异常时,异常会被调用方静默处理,相当于返回了 Duration.ZERO,即立即过期。
*/
Duration getExpiryForCreation(K key, V value);
/**
* 缓存项被命中时,用返回值重置该缓存项的过期时间。
* 返回值为 null 表示不重置过期时间
* 当该方法抛出异常时,异常会被调用方静默处理,相当于返回了 Duration.ZERO,即立即过期。
*/
Duration getExpiryForAccess(K key, ValueSupplier<? extends V> value);
/**
* 缓存项被更新时,用返回值重置该缓存项的过期时间。
* 返回值为 null 表示不重置过期时间
* 当该方法抛出异常时,异常会被调用方静默处理,相当于返回了 Duration.ZERO,即立即过期。
*/
Duration getExpiryForUpdate(K key, ValueSupplier<? extends V> oldValue, V newValue);
演示代码如下:
gordon.study.cache.ehcache3.pattern.CustomExpiryCacheAsideUserService.java
private static final UserModel NULL_USER = new NullUser();
private Ehcache<String, UserModel> cache;
public CustomExpiryCacheAsideUserService() {
cache = (Ehcache<String, UserModel>) UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, UserModel.class)
.withExpiry(new CustomUserExpiry(new Duration(100, TimeUnit.SECONDS))).build(true);
}
public UserModel findUser(String id) {
UserModel cached = cache.get(id);
if (cached != null) {
System.out.println("get user from cache: " + id);
return cached;
}
UserModel user = null;
if (!id.equals("0")) {
user = new UserModel(id, "info ..."); // find user
}
System.out.println("get user from db: " + id);
if (user == null) {
user = NULL_USER;
}
cache.put(id, user);
return user;
}
private static class CustomUserExpiry implements Expiry<String, UserModel> {
private final Duration ttl;
public CustomUserExpiry(Duration ttl) {
this.ttl = ttl;
}
@Override
public Duration getExpiryForCreation(String key, UserModel value) {
if (value.isNull()) {
System.out.println("user is null: " + key);
return new Duration(10, TimeUnit.SECONDS);
}
long length = ttl.getLength();
if (length > 10) {
long max = length / 5;
long random = ThreadLocalRandom.current().nextLong(-max, max);
return new Duration(ttl.getLength() + random, ttl.getTimeUnit());
}
return ttl;
}
@Override
public Duration getExpiryForAccess(String key, ValueSupplier<? extends UserModel> value) {
return null;
}
@Override
public Duration getExpiryForUpdate(String key, ValueSupplier<? extends UserModel> oldValue, UserModel newValue) {
return ttl;
}
}
public static void main(String[] args) throws Exception {
final CustomExpiryCacheAsideUserService service = new CustomExpiryCacheAsideUserService();
for (int i = 0; i < 5; i++) {
service.findUser("" + i);
}
}
CustomUserExpiry 类似于 TimeToLiveExpiry,唯一区别是当缓存项被创建时,返回的 Duration 会在原来的基础上随机浮动 20%。考虑到并发性,随机数生成器用的是 ThreadLocalRandom。CustomUserExpiry 类通过代码第7行 withExpiry 方法设置。这用来解决缓存过期问题。
至于缓存穿透问题,首先用 Null Object 模式修改 UserModel 类,增加 isNull 方法用于判断是否为 null object,简单起见,该方法直接返回 false。再定义 NullUser 类,继承自 userModel,其 isNull 方法返回 true。
public class UserModel {
public boolean isNull() {
return false;
}
public static class NullUser extends UserModel {
public NullUser() {
super(null, null);
}
public boolean isNull() {
return true;
}
}
}
代码第21行,当 SoR 返回的查询结果为 null 时,使用第1行预先定义好的 NullUser 实例作为返回值,同时将本次查询结果放入缓存。
代码第38行,如果即将创建的缓存是 null object,则只缓存10秒钟。
I. 过期算法略读
Ehcache3 的回收判定发生在 put 操作时,而过期判定则发生在 get 操作时。
Ehcache 类的 get 方法调用其 Store store 属性的 get 方法,尝试获得缓存的数据。Store 代表缓存的各种存储方式。
本例中,Store 的具体实现类为 OnHeapStore,表示使用堆空间存储缓存项。它的 get 方法调用其 Backend map 属性的 get 方法尝试获得缓存的数据。Backend 通过泛型屏蔽底层 map 的键类型。
本例中,Backend 的具体实现类为 SimpleBackend,它拥有 org.ehcache.impl.internal.concurrent.ConcurrentHashMap<K, OnHeapValueHolder> realMap 存储缓存项,该 ConcurrentHashMap 基本 copy 自 JDK 的 ConcurrentHashMap,只是增加了几个方法。SimpleBackend 的 get 方法就是调用该 ConcurrentHashMap 的 get 方法尝试获得缓存的数据。
OnHeapStore 从最底层的 ConcurrentHashMap 获取到缓存项后,调用 OnHeapValueHolder 的 isExpired 方法判断缓存项是否过期:
@Override
public boolean isExpired(long expirationTime, TimeUnit unit) {
final long expire = this.expirationTime;
if (expire == NO_EXPIRE) {
return false;
}
return expire <= nativeTimeUnit().convert(expirationTime, unit);
}
OnHeapValueHolder 的 expirationTime 属性用于判断缓存项是否过期。方法参数 long expirationTime 传入的是当前系统时间,如果 OnHeapValueHolder 的 expirationTime 小于当前系统时间,则该缓存项已经过期。对于过期的缓存项,会将之从 ConcurrentHashMap 移除,因此 Ehcache 的 get 方法会返回 null。
Ehcahce3 的这种设计方式导致 Null Object 模式优化的缓存穿透预防方案有点奇怪。一般情况下,null object 不会被再次 get,就不会被过期算法直接移除,另一方面,按照默认回收算法只比较 lastAccessTime 值,因此为 null object 设置的很短的失效时间实际上很可能没有作用。
个人觉得,put 操作引发的回收算法中可以增加过期判断,如果发现过期数据,优先回收这些数据,可以缓解明明缓存空间中有过期数据,却回收尚未过期的数据这种情况。
缓存技术内部交流_04_Cache Aside续篇的更多相关文章
- 缓存技术内部交流_05_Cache Through
参考资料: http://www.ehcache.org/documentation/3.2/caching-patterns.html http://www.ehcache.org/document ...
- 缓存技术内部交流_01_Ehcache3简介
参考资料: http://www.ehcache.org/documentation/3.2/getting-started.html http://www.ehcache.org/documenta ...
- 缓存技术内部交流_03_Cache Aside
参考资料: http://www.ehcache.org/documentation/3.2/caching-patterns.html http://www.ehcache.org/document ...
- 缓存技术内部交流_02_Ehcache3 XML 配置
参考资料: http://www.ehcache.org/documentation/3.2/getting-started.html#configuring-with-xml http://www. ...
- .Net环境下的缓存技术介绍 (转)
.Net环境下的缓存技术介绍 (转) 摘要:介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1 概念 ...
- [.net 面向对象程序设计进阶] (14) 缓存(Cache) (一) 认识缓存技术
[.net 面向对象程序设计进阶] (14) 缓存(Cache)(一) 认识缓存技术 本节导读: 缓存(Cache)是一种用空间换时间的技术,在.NET程序设计中合理利用,可以极大的提高程序的运行效率 ...
- .Net环境下的缓存技术介绍
.Net环境下的缓存技术介绍 摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1 概念 1.1 ...
- ASP.NET 缓存技术分析
缓存功能是大型网站设计一个很重要的部分.由数据库驱动的Web应用程序,如果需要改善其性能,最好的方法是使用缓存功能.可能的情况下尽量使用缓存,从内存中返回数据的速度始终比去数据库查的速度快,因而可以大 ...
- 强大的Spring缓存技术(上)
缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配置方法,展现了 ...
随机推荐
- java.util.Calendar
package day14; import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer; import java.util.Cal ...
- js如何转义和反转义html特殊字符
“<”如何反转义为“<”,“>”如何反转义为“>”,下面就介绍如何用js来实现这种类似的操作. //HTML转义 function HTMLEncode(html) { var ...
- dijstra算法,求源点到各个顶点的最短距离
1:dijstra算法常用语求最短距离, dijstra每次从未发现节点n[]中,发现距离源点最短的节点m,求出最短节点后,将m添加到已发现节点y[]中,用该节点m进行更新其它未发现节点n[]-m的最 ...
- django之自定义软删除Model
软删除 简单的说,就是当执行删除操作的时候,不正真执行删除操作,而是在逻辑上删除一条记录.这样做的好处是可以统计数据,可以进行恢复操作等等. 预备知识 Managers Managers 是djang ...
- 20165324 学习基础与C语言学习心得
20165324 技能学习心得与C语言学习 一.读后感及技能学习 做中学读后感 我认为给学生具体的, 能实践的, 能马上看到因果关系的教材和练习, 是激发学生兴趣, 好奇心, 求知欲的好方法. 我就是 ...
- JDK环境变量配置目录jre,jvm
类路径 :CLASSPATH= .;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/lib/tools.jar JDK的路径:JAVA_HOME = C:/Program F ...
- 84. Largest Rectangle in Histogram(直方图最大面积 hard)
Given n non-negative integers representing the histogram's bar height where the width of each bar is ...
- The 15th UESTC Programming Contest Preliminary J - Jermutat1on cdoj1567
地址:http://acm.uestc.edu.cn/#/problem/show/1567 题目: Jermutat1on Time Limit: 3000/1000MS (Java/Others) ...
- [转]madwifi无线网卡源代码阅读
转自:http://xiyong8260.blog.163.com/blog/static/66514621200892465922669/ 在我的Doctor课题研究中,基于ARF协议设计了一个改进 ...
- Salesforce中Html的转义,InputField和RemoteAction
在Salesforce的开发中,有时候需要在对象中插入记录,其中有的字段需要插入Html,但是对于输入Html的域,大多数框架和网站都需要做Html的转义处理,防止XSS或者SQL注入攻击.有时候我们 ...