感谢海哥分享,原文来自:http://www.yangfuhai.com/post/31.html
最近为了提高JPress的性能,减少数据查询的次数,JPress大量使用了ehcache缓存作为起内置缓存,同时session也是基于ehcache重新实现的支持分部署的session解决方案。
因为JPress是基于JFinal快速开发框架,而JFinal又内置了ehcache的插件,使用起来及其简单。
1、JFinal里配置ehcachePlugin插件。
public void configPlugin(Plugins me) {
me.add(new EhCachePlugin());
//添加其他插件
}
2、在classPath下添加ehcache的配置文件ehcache.xml
3、直接使用EhcacheKit操作缓存。
public void yourMethod() {
Cachekit.put("cacheName","key","value")
}
到此,一切很顺利的进行着,但随着JPress在大量的使用ehcache,ehcache的缓存数据操作与更新就变成了一个棘手的问题,更新数据库数据了,缓存若得不到及时更新,就会导致程序在运行的过程中有大量的bug、各种莫名其妙的问题。此时、缓存数据的更新,就需要一个良好更新的计划和方案。
首先,是数据颗粒度的问题,我们在缓存数据的时候,可能是根据数据库的ID,对单个model(单条数据)进行缓存,这种缓存以model的ID作为key进行缓存,这种缓存的颗粒度极细。
因此,我们在做数据的更新的时候非常简单,只需在model的更新和删除的时候从ehcache删除该ID即可。针对这一的问题,我们只需要重写Model的update和delete方法,删除其缓存。
代码如下:
@Table(tableName = "content", primaryKey = "id")
public class Content extends BaseContent {
private static final long serialVersionUID = 1L;
@Override
public boolean update() {
removeCache(getId());//移除ehcache缓存
return super.update();
}
@Override
public boolean delete() {
removeCache(getId());//移除ehcache缓存
return super.delete();
}
}
通过这种方式,我们在通过ID来查询该数据的时候,不用担心缓存于数据库不同步的问题,因为我们在更新、删除的时候就已经把ehcache的缓存数据给清除掉了,当查询的时候发现ehcache里没有数据,自动会去数据库会获取,从而保证了ehcache的数据与数据库保持一致。
但是,我们在缓存数据的时候,不只是对单个model进行缓存,在程序的各种业务场景中,大量会使用到列表的查询,因此我们在存储的时候,肯定也会多列表进行缓存。例如:
public List findByModule(final String module, final BigInteger parentId, String orderby) {
final StringBuilder sqlBuilder = new StringBuilder("select * from content c");
sqlBuilder.append(" sql ....");
return DAO.getFromListCache("cacheName","key", new IDataLoader() {
@Override
public Object load() {
return DAO.find(sqlBuilder.toString(), module,parentId);
}
});
}
但是,一旦缓存了列表,问题就来了?这个列表的数据什么时候会被更新呢?这个缓存到ehcache的某条数据可能会被随时更新或删除了,怎么来同步?
一种粗糙的方案是:把所有缓存都缓存到同一个cacheName中,然后在model的update或delete的时候,对这个cacheName不管三七二十一直接全部清除,如下代码:
@Table(tableName = "content", primaryKey = "id")
public class Content extends BaseContent {
private static final long serialVersionUID = 1L;
@Override
public boolean update() {
removeCache(getId());//移除ehcache缓存
removeAllListCache(); //移除所有保存列表数据的缓存
return super.update();
}
@Override
public boolean delete() {
removeCache(getId());//移除ehcache缓存
removeAllListCache(); //移除所有保存列表数据的缓存
return super.delete();
}
}
虽然这是一种粗糙的方案,但是也是有效解决了列表数据不同步的问题;其粗糙的原因是,当我们清除数据的时候,把所有的列表都删除了,这样会导致很多没有没有该列表的数据也被清楚了...
所以,更有效的解决方案应该是保留和该ID没有关系的数据,而只清除有关的数据。
那问题来了,什么数据才是该ID有关的数据呢?
1、列表有该ID的数据。
2、列表的排序等会受到该ID影响的数据,比如谋条数据的orderby_number更新了,可能某个缓存的列表数据虽然没有该ID,但是该ID更新后,可能是orderby_number,由于缓存的列表数据是根据orderby_number来排序的,此时该数据应该出现在列表里。
3、分页数据,比如某条数据被删除或更新了,可能分页的页码数据就会被改变。
那如何才能找到该ID关联的数据呢?
这是一个困难的问题,每个业务系统不一样,关联的数据肯定也不一样。在JPress里,每个content都有一个module字段,表示该数据所属的模型。
因此,在JPress的内容分类里,JPress针对某种类型的数据,都按照一定的规则来建立这个存储的key,比如文章模型的列表在存储的时候,存储的key值大概为:module:article-xxx-xxx这样的key。
当文章模型的数据被更新的时候,会去便利所有列表数据的key,如果发现key是以module:article开头,表示该数据是文章列表的缓存数据,应该清除。
于是,就有了如下的代码:
public void removeAllListCache() {
List