参考资料:

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

http://www.ehcache.org/documentation/3.2/usermanaged.html(optional)

示例代码:

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

A. 缓存模式(Caching Patterns)

缓存模式有两种,一种是 Cache Aside,一种是 Cache-As-SoR(system-of-record)。

在 Cache Aside 模式中,应用程序直接操作缓存与系统数据,由应用程序保证缓存的有效性。而在 Cache-As-SoR 模式中,应用程序只能看见缓存,缓存层有自己的 loader 模块,负责与系统数据的交互。

B. Ehcache3 实现 Cache Aside 模式

gordon.study.cache.ehcache3.pattern.CacheAsideUserService.java

    private UserManagedCache<String, UserModel> cache;

    public CacheAsideUserService() {
cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, UserModel.class).build(true);
} public UserModel findUser(String id) {
UserModel cached = cache.get(id);
if (cached != null) {
System.out.println("get user from cache");
return cached;
}
UserModel user = new UserModel(id, "info ..."); // find user
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("get user from db");
cache.put(id, user);
return user;
} public UserModel updateUser(String id, String info) {
UserModel user = new UserModel(id, info); // update user
cache.put(id, user);
return user;
} public boolean deleteUser(String id) {
// delete user
cache.remove(id);
return true;
} public static void main(String[] args) {
final CacheAsideUserService service = new CacheAsideUserService();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
public void run() {
service.findUser("1");
}
});
}
executorService.shutdown();
}

代码第1行定义了一个 user managed caches,这种 cache 不需要 CacheManager 管理。

findUser 方法首先尝试从缓存中获取数据,如果缓存中没有相应数据,则从 SoR 中获取数据并将之放入缓存。

updateUser 方法修改完 SoR 后会更新缓存中的数据。

deleteUser 方法删除 SoR 中记录后会移除缓存中的数据。

以上就是一个典型的 Cache Aside 示例。

C. Cache Aside 模式的问题:并发读

Cache Aside 模式存在一些问题,第一是并发读的问题,在高并发场景下,当多个请求同时访问一条数据时,如果此时数据不在缓存中(例如刚过期),则这些请求会同时去后端 SoR 中获取数据,瞬间压力巨大。

上面示例代码 main 函数执行时会打印10条 "get user from db",表示所有线程同时访问了后端 SoR。

解决这个问题的办法是引入同步机制,保证只会有一个线程去后端 SoR 中获取数据,其余线程等待数据进入缓存后直接从缓存获取,减轻 SoR 端的压力。

gordon.study.cache.ehcache3.pattern.SyncCacheAsideUserService.java

    private UserManagedCache<String, UserModel> cache;

    private final ReentrantLock lock = new ReentrantLock();

    public SyncCacheAsideUserService() {
cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, UserModel.class).build(true);
} public UserModel findUser(String id) {
UserModel result = cache.get(id);
if (result == null) {
lock.lock();
result = cache.get(id);
if (result == null) {
result = new UserModel(id, "info ..."); // find user
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("get user from db: " + id);
cache.put(id, result);
} else {
System.out.println("get user from cache after competition: " + id);
}
lock.unlock();
} else {
System.out.println("get user from cache: " + id);
}
return result;
} public static void main(String[] args) {
final SyncCacheAsideUserService service = new SyncCacheAsideUserService();
ExecutorService executorService = Executors.newFixedThreadPool(30);
for (int i = 0; i < 10; 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();
}

通过加锁,保证了只有竞争 lock 成功的线程才能去后端 SoR 获取数据,其它线程只能等待。

从 SyncCacheAsideUserService 的输出发现了新的问题:现在从 SoR 获取数据变成了串行的方式,对不同的 id 的查找操作也被 lock 锁给同步了。

直觉上,我们会想到为每个不同的 id 使用不同的锁。为了查找效率,这些锁需要放置在 Map 结构中,通过 id 为 key 方便检索。最后,由于 id 可取值范围太广,普通的 map 会造成内存泄漏,因此考虑使用 WeakHashMap 解决内存泄漏问题,这样便产生了以下试验性代码(演示用,不要用于生产环境)

gordon.study.cache.ehcache3.pattern.SyncByIdCacheAsideUserService.java

    private UserManagedCache<String, UserModel> cache;

    private final Map<String, ReentrantLock> lockMap = new WeakHashMap<>();

    private final ReentrantLock lockMapLock = new ReentrantLock();

    public SyncByIdCacheAsideUserService() {
cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, UserModel.class).build(true);
} public UserModel findUser(String id) {
UserModel result = cache.get(id);
if (result == null) {
lockMapLock.lock();
ReentrantLock lock = lockMap.get(id);
if (lock == null) {
lock = new ReentrantLock();
lockMap.put(new String(id), lock);
}
lockMapLock.unlock(); lock.lock();
result = cache.get(id);
if (result == null) {
result = new UserModel(id, "info ..."); // find user
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("get user from db: " + id);
cache.put(id, result);
} else {
System.out.println("get user from cache after competition: " + id);
}
lock.unlock();
} else {
System.out.println("get user from cache: " + id);
}
return result;
} public static void main(String[] args) throws Exception {
final SyncByIdCacheAsideUserService service = new SyncByIdCacheAsideUserService();
ExecutorService executorService = Executors.newFixedThreadPool(30);
for (int i = 0; i < 10; 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.gc();
Thread.sleep(1000);
System.out.println(service.lockMap.size());
}

D. Cache Aside 模式的问题:并发读写

当读写操作并发存在时,理论上有缓存旧数据的可能。可能场景如下:

  • 线程A读取用户,缓存未命中,于是从 SoR 中读取到旧数据
  • 线程B更新用户,将 SoR 中数据更新,然后更新缓存
  • 线程A将旧数据更新到缓存

但是这种场景仅仅是理论上可能,因为在 SoR 中更新数据一般是比较费时的操作(而且数据库写操作会锁表,读操作必然是要在锁表前读取出旧值的),线程A没理由会在这么长的时间段内没将旧数据更新到缓存。所以一般情况下,我们会无视这种情况。

E. Cache Aside 模式的问题:并发写

并发写也可能导致缓存旧数据。可能场景如下:

  • 线程A更新用户到版本2
  • 线程B更新用户到版本3,然后更新缓存
  • 线程A用版本2的数据更新缓存

同上,这种场景发生概率也很低,尤其是对于用户模块来说,很少有并发写的场景,所以可以不用太考虑,只要设定合适的缓存过期时间就可以了。

不过,解决并发写问题的方法很简单,只要在 update 操作后对缓存执行移除操作而不是更新操作就可以了。

缓存技术内部交流_03_Cache Aside的更多相关文章

  1. 缓存技术内部交流_05_Cache Through

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

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

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

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

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

  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. MyEclipse 10的使用技巧

    默认快捷键 :Shift+Alt+s 然后选择generater getter and setter,这是快捷键.或者右键source里边有 generater getter and setter. ...

  2. python学习笔记(九)函数返回多个值,列表生成式,循环多个变量,入参格式声明

    一.函数返回多个值 1.函数如果返回多个值的话,它会把这几个值放到一个元组里面2.函数如果返回多个值的话,也可以用多个变量来接收 def say(): num1 = num2 = num3 = ret ...

  3. Linux 最常用的20条命令

    1.cd命令 这是一个非常基本,也是大家经常需要使用的命令,它用于切换当前目录,它的参数是要切换到的目录的路径,可以是绝对路径,也可以是相对路径.如:   cd /root/Docements # 切 ...

  4. SQL Server窗口框架——ROWS、RANGE

    说到窗口框架就不得不提起开窗函数. 开窗函数支持分区.排序和框架三种元素,其语法格式如下: OVER ( [ <PARTITION BY clause> ] [ <ORDER BY ...

  5. obj比较

    <script> var obj1={name:'one',age:30}; var obj2={name:'one',age:30}; //比较两个字符串对象的[值]是否相等 Objec ...

  6. 端口状态说明 LISTENING、ESTABLISHED、TIME_WAIT及CLOSE_WAIT

    TCP状态转移要点    TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不会被释放.网络服务器程序要同时 ...

  7. 一道仅有7人通过的超5星微软比赛题目-------解题思路&优秀代码分享,邀你来“找茬儿”

    6月23日英雄会平台发布了一道难度为超5星的微软比赛题目,截止活动结束共有300多名编程爱好者参与线上答题,而最终通过者仅有7人,通过率仅为2%.为什么成绩如此出人意料?是因为题目的英文描述难以理解? ...

  8. (译)Windows Azure:移动后端开发的主要更新

    Windows Azure:移动后端开发的主要更新 这周我们给Windows Azure释出了一些很棒的更新,让云上的移动应用开发明显的简单了.这 些新功能包括: 移动服务:定制API支持移动服务:G ...

  9. MySQL中exists与in的使用

    exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当 exists里的条件语句能够返回记录行时(无论记录行是的多少,只要能返回),条件就为真,返回当前loop到的这条记录, ...

  10. Wex5各组件介绍

    1.http://doc.wex5.com/comp-base/ 2.select 组件 http://doc.wex5.com/comps-select/ 3.页面交互以及传递参数  http:// ...