缓存技术内部交流_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 配置方法,展现了 ...
随机推荐
- ObjectDetection中的一些名词中英文对照
mAP:mean Average Precision,平均精确度 recall rate:召回率 Loss Function Anchro
- 微信iOS版更新:可批量管理不常联系的朋友
iOS版微信更新了v6.5.13版本,在新版本当中微信新增加了可批量管理不常联系的朋友功能,同时在群资料页可以查看最近收到的小程序,不过据网友爆料,腾讯在新的更新日志当中已经删除了“批量管理不常联系的 ...
- 单例Singleton模式的两种实现方法
在设计模式中,有一种叫Singleton模式的,用它可以实现一次只运行一个实例.就是说在程序运行期间,某个类只能有一个实例在运行.这种模式用途比较广泛,会经常用到,下面是Singleton模式的两种实 ...
- 小tip: 使用SVG寥寥数行实现圆环loading进度效果
二.正文 设计师设计了一个图片上传圆环loading进度效果.如下截图: 首先,CSS3是可以实现的,以前写过一篇转大饼的文章:“CSS3实现鸡蛋饼饼状图loading等待转转转”.原理跟这个一模一样 ...
- MySQL8.0新特性
一.修改密码修改root密码之前要先flush privileges;ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'f ...
- Django restful Framework 之序列化与反序列化
1. 首先在已建好的工程目录下新建app命名为snippets,并将snippets app以及rest_framework app加到工程目录的 INSTALLED_APPS 中去,具体如下: IN ...
- hdu4300 Clairewd’s message
地址:http://acm.hdu.edu.cn/showproblem.php?pid=4300 题目: Clairewd’s message Time Limit: 2000/1000 MS (J ...
- react build 后打包发布总结
一,部署在apache web服务器上(wamp | xammp) 1.后台接口需要做跨域设置 (1)在服务端利用Access-Control-Allow-Origin响应头解决. 设置A ...
- 20145316许心远《Java学习笔记》第三周总结
20145316许心远<Java程序设计>第3周学习总结 教材学习内容总结 一.定义类: 类定义时使用class关键字 如果要将x绑定到新建的对象上,可以使用"="制定 ...
- genisoimage命令用法
功能说明:建立ISO 9660映像文件. 常用命令:genisoimage -o imagename.iso file 语 法:mkisofs [-adDfhJlLNrRTvz][-print-si ...