参考资料:

http://www.ehcache.org/documentation/3.2/caching-patterns.html

http://www.ehcache.org/documentation/3.2/writers.html

示例代码:

https://github.com/gordonklg/study,cache module

A. Cache-As-SoR

前文提过 Cache-As-SoR 模式。老外抽象概念的能力比较泛滥,简而言之,Cache-As-SoR 意味着使用者把缓存层当做系统数据层用,为了同步数据,读模式有 read-through,写模式有 write-through 和 write-behind,读写模式中各取一个组合成 Cache-As-SoR 模式。而文章标题中的 Cache Through 是指 read-through 和 write-through 的组合。

在 Read-Through 模式中,缓存拥有一个 loader 模块,当应用系统访问缓存层获取数据时,如果数据当下不在缓存中,则由 loader 负责从 SoR 获取数据,放入缓存,并返回给应用系统。

在 Write-Through 模式中,当应用系统更新缓存层数据时,由 writer 模块将数据写入 SoR,并更新缓存。

在 Write-Behind 模式中,更新的数据会先存放在本地队列,然后批量写入 SoR。

B. Ehcache3 实现 Cache Through 模式

Ehcache3 通过 CacheLoaderWriter 来实现 Cache-As-SoR 模式,相当于把 loader 和 writer 模块合到了一起。见下例:

gordon.study.cache.ehcache3.pattern.CacheThroughUserService.java

    private UserManagedCache<String, UserModel> cache;

    public CacheThroughUserService() {
cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, UserModel.class).withLoaderWriter(new CustomLoaderWriter())
.build(true);
} public UserModel findUser(String id) {
UserModel result = cache.get(id);
if (result == null) {
System.out.println("can't find user in cache!");
} else {
System.out.println("get user from cache: " + id);
}
return result;
} public UserModel updateUser(String id, String info) {
UserModel user = new UserModel(id, info);
cache.put(id, user);
return user;
} public boolean deleteUser(String id) {
cache.remove(id);
return true;
} public static void main(String[] args) throws Exception {
final CacheThroughUserService service = new CacheThroughUserService();
ExecutorService executorService = Executors.newFixedThreadPool(30);
for (int i = 0; i < 3; i++) {
executorService.execute(new Runnable() {
public void run() {
service.findUser("1");
}
});
executorService.execute(new Runnable() {
public void run() {
service.findUser("2");
}
});
executorService.execute(new Runnable() {
public void run() {
service.findUser("3");
}
});
}
executorService.shutdown();
executorService.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("---------------------------------");
service.updateUser("1", "new info ...");
service.findUser("1");
service.deleteUser("1");
service.findUser("1");
} private static class CustomLoaderWriter implements CacheLoaderWriter<String, UserModel> { @Override
public UserModel load(String key) throws Exception {
System.out.println("::load user by id: " + key);
Thread.sleep(1000);
if (key == null || key.equals("1")) {
return new NullUser();
}
return new UserModel(key, "info ...");
} @Override
public Map<String, UserModel> loadAll(Iterable<? extends String> keys) throws BulkCacheLoadingException, Exception {
System.out.println("::load user by ids: " + keys);
return null;
} @Override
public void write(String key, UserModel value) throws Exception {
System.out.println("::update user by id: " + key);
} @Override
public void writeAll(Iterable<? extends Entry<? extends String, ? extends UserModel>> entries)
throws BulkCacheWritingException, Exception {
System.out.println("::update user by ids");
} @Override
public void delete(String key) throws Exception {
System.out.println("::delete user by id: " + key);
} @Override
public void deleteAll(Iterable<? extends String> keys) throws BulkCacheWritingException, Exception {
System.out.println("::delete user by ids");
}
}

代码第58行定义的 CustomLoaderWriter 实现了 CacheLoaderWriter 接口。其 load、write、delete 方法分别用于从 SoR 层读取、写入、删除数据。

代码第4行通过 withLoaderWriter 方法设置 loaderWriter,激活 Cache-As-SoR 模式。

Service 层得到极大的精简,只需要无脑操作缓存即可。

从控制台输出可以看出,Cache-As-SoR 比较好的解决了并发读的问题,多个线程有机会同时读取后端数据。

C. 源码分析

CacheLoaderWriter 是如何解决并发读的问题的呢?

首先,要了解 ConcurrentHashMap 有个 compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) 方法,该方法用参数传入的 BiFunction 为指定的 key 设置一个计算出的值,同时保证整个计算过程的原子性:其它线程如果也想更新目标 key 的值,会被阻塞直到 compute 方法完成计算。(实现细节有点难读,需要了解 CAS 原理和 ConcurrentHashMap 设计细节,只看了个大概,以后有机会专门深入研究一下)

剩下的实现就简单了,当 cache builder 指定了 cacheLoaderWriter 时,最终构建的内部 cache 为 EhcacheWithLoaderWriter。EhcacheWithLoaderWriter 的 get 方法最终会调用底层 ConcurrentHashMap 的 compute 方法,通过 CacheLoaderWriter 的 load 方法从 SoR 中获取数据。

为了防止多次调用 CacheLoaderWriter 的 load 方法从 SoR 中获取数据,本来应该要调用 ConcurrentHashMap 的 computeIfAbsent 方法才对。Ehcache3 中把判断 if absent then compute 的逻辑放到了 OnHeapStore 的 computeIfAbsent 方法中,这样做的最大目的应该是为了保证 get 操作会触发过期逻辑。

  public ValueHolder<V> computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) throws StoreAccessException {
computeIfAbsentObserver.begin();
checkKey(key); final StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
final long now = timeSource.getTimeMillis(); final AtomicReference<OnHeapValueHolder<V>> previousValue = new AtomicReference<OnHeapValueHolder<V>>();
final AtomicReference<StoreOperationOutcomes.ComputeIfAbsentOutcome> outcome =
new AtomicReference<StoreOperationOutcomes.ComputeIfAbsentOutcome>(StoreOperationOutcomes.ComputeIfAbsentOutcome.NOOP);
OnHeapValueHolder<V> computeResult = map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
@Override
public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
if (mappedValue == null || mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
if (mappedValue != null) {
updateUsageInBytesIfRequired(- mappedValue.size());
fireOnExpirationEvent(mappedKey, mappedValue, eventSink);
}
V computedValue = mappingFunction.apply(mappedKey);
if (computedValue == null) {
return null;
} checkValue(computedValue);
OnHeapValueHolder<V> holder = newCreateValueHolder(key, computedValue, now, eventSink);
if (holder != null) {
outcome.set(StoreOperationOutcomes.ComputeIfAbsentOutcome.PUT);
updateUsageInBytesIfRequired(holder.size());
}
return holder;
} else {
previousValue.set(mappedValue);
outcome.set(StoreOperationOutcomes.ComputeIfAbsentOutcome.HIT);
OnHeapValueHolder<V> holder = setAccessTimeAndExpiryThenReturnMappingUnderLock(key, mappedValue, now, eventSink);
if (holder == null) {
updateUsageInBytesIfRequired(- mappedValue.size());
}
return holder;
}
}
});

上面第15行代码保证了如果底层 ConcurrentHashMap 中的缓存项已过期,会重新从 SoR 中读取。

缓存技术内部交流_05_Cache Through的更多相关文章

  1. 缓存技术内部交流_04_Cache Aside续篇

    额外参考资料: http://www.ehcache.org/documentation/3.2/expiry.html F. Cache Aside 模式的问题:缓存过期 有时我们会在上线前给缓存系 ...

  2. 缓存技术内部交流_01_Ehcache3简介

    参考资料: http://www.ehcache.org/documentation/3.2/getting-started.html http://www.ehcache.org/documenta ...

  3. 缓存技术内部交流_03_Cache Aside

    参考资料: http://www.ehcache.org/documentation/3.2/caching-patterns.html http://www.ehcache.org/document ...

  4. 缓存技术内部交流_02_Ehcache3 XML 配置

    参考资料: http://www.ehcache.org/documentation/3.2/getting-started.html#configuring-with-xml http://www. ...

  5. .Net环境下的缓存技术介绍 (转)

    .Net环境下的缓存技术介绍 (转) 摘要:介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 ...

  6. [.net 面向对象程序设计进阶] (14) 缓存(Cache) (一) 认识缓存技术

    [.net 面向对象程序设计进阶] (14) 缓存(Cache)(一) 认识缓存技术 本节导读: 缓存(Cache)是一种用空间换时间的技术,在.NET程序设计中合理利用,可以极大的提高程序的运行效率 ...

  7. .Net环境下的缓存技术介绍

    .Net环境下的缓存技术介绍 摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 1.1 ...

  8. ASP.NET 缓存技术分析

    缓存功能是大型网站设计一个很重要的部分.由数据库驱动的Web应用程序,如果需要改善其性能,最好的方法是使用缓存功能.可能的情况下尽量使用缓存,从内存中返回数据的速度始终比去数据库查的速度快,因而可以大 ...

  9. 强大的Spring缓存技术(上)

    缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配置方法,展现了 ...

随机推荐

  1. 基于Hadoop的数据仓库Hive

    Hive是基于Hadoop的数据仓库工具,可对存储在HDFS上的文件中的数据集进行数据整理.特殊查询和分析处理,提供了类似于SQL语言的查询语言–HiveQL,可通过HQL语句实现简单的MR统计,Hi ...

  2. Linux下BLAST的使用---转载

    1.把BLAST的压缩文件解压,然后将bin目录下的文件拷贝至/usr/local/bin下:2.制作软链接,将解压后的文件中bin目录链接至/home/username下,eg:ln -s /hom ...

  3. co.js异步回调原理理解

    co.js是基于es6的generator实现的,相当于generator函数的一个自动执行器 generator的简单介绍 function* fn(){ before() yield firstY ...

  4. Teleport Ultra 垃圾代码 tppabs的清理<转>

    在使用整站下载软件Teleport Pro或Teleport Ultra下载的离线文件里会包含大量垃圾代码,下载后就需要清除整站下载文件中的冗余代码:tppabs等.这些代码本是Teleport自动添 ...

  5. hbase(一)

    1.hbase安装参考 http://blog.csdn.net/wild46cat/article/details/53214159 2.遇到的问题: ERROR: The node /hbase ...

  6. The 15th UESTC Programming Contest Preliminary J - Jermutat1on cdoj1567

    地址:http://acm.uestc.edu.cn/#/problem/show/1567 题目: Jermutat1on Time Limit: 3000/1000MS (Java/Others) ...

  7. 实战DVWA!

    DVWA漏洞训练系统,来个大图^-^ 1.首先试了下DVWA的命令执行漏洞command execution     这是我在Low级别上测试的,另外附上low级别代码: <?php if( i ...

  8. USB Transfer and Packet Sizes

    https://msdn.microsoft.com/en-us/library/ff538112.aspx http://blog.csdn.net/chenyujing1234/article/d ...

  9. make menuconfig错误的解决办法

    如果使用make menuconfig的方式配置内核,又碰巧系统没有安装ncurses库(ubuntu系统默认就没有安装此库),就会出现错误,错误信息大体上如下: *** Unable to find ...

  10. 解读:CombineFileInputFormat类

    MR-Job默认的输入格式FileInputFormat为每一个小文件生成一个切片.CombineFileInputFormat通过将多个“小文件”合并为一个"切片"(在形成切片的 ...