精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-05-14
最后修改:2012-05-14
因为我们有必要做出一种灵活性和性能更好的缓存来适应这种情况。spring cache已经提供了一个良好的框架和cache annotation给我们使用,但是我们需要那他来开刀,改造为我们想要的效果。由于目前对spring cache的应用介绍都是点到为止,因此我们可以直接从spring 的代码和它的文档入手。 目标 提高系统性能,在有限的资源下支持更多的用户并发量 取消并模拟Hibernate的二级缓存机制,整合Hibernate和JDBC的缓存 更高的灵活性和实时性 原则 1. 针对系统中不改动或者少量改动的数据对象,不适宜用在频繁改动的数据上 2. 缓存越靠近系统前端,表现越好 关键点 1. 设计缓存的关键字 "key" 2. 设计缓存的更新机制 3. 缓存以key - value 方式存在 .......... 具体介绍已经写在ppt中,因为这个思路不一定成熟,欢迎讨论。代码如下: spring cache 配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd"> <cache:annotation-driven key-generator="stringKeyGenerator"/> <bean id="cacheManager" class="com.legendshop.business.cache.EhCacheCacheManager" p:cache-manager-ref="ehcacheFactory" p:supportQueryCache="true"/> <!-- Ehcache library setup --> <bean id="ehcacheFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:ehcache.xml" /> <bean id="stringKeyGenerator" class="com.legendshop.core.cache.StringKeyGenerator" /> LegendCacheManager:继承与CacheManager,增加自己的属性: package com.legendshop.business.cache; import org.springframework.cache.CacheManager; /** * The Interface LegendCacheManager. */ public interface LegendCacheManager extends CacheManager{ /** * * Checks if is support query cache. * 是否支持查询缓存 * * @return true, if is support query cache */ public boolean isSupportQueryCache(); /** * Checks if is removes the all entries. * 在不支持查询缓存的情况下起效,当Entity Cache更新时是否删除List Cache所有对象 * * @return true, if is removes the all entries */ public boolean isRemoveAllEntries(); /** * Gets the rel cache name. * RelationShop 缓存名称 * * @return the rel cache name */ public String getRelCacheName(); } EhCacheCacheManager: Ehcache Manager 的实现类 import java.util.Collection; import java.util.LinkedHashSet; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Status; import org.springframework.cache.Cache; import org.springframework.cache.support.AbstractCacheManager; import org.springframework.util.Assert; /** * The Class EhCacheCacheManager. */ public class EhCacheCacheManager extends AbstractCacheManager implements LegendCacheManager { /** The cache manager. */ private net.sf.ehcache.CacheManager cacheManager; /** The support query cache. */ private boolean supportQueryCache; /** The remove all entries. */ private boolean removeAllEntries; /** The rel cache name. */ private String relCacheName = "LEGENDSHOP_CACHE"; /** * Set the backing EhCache {@link net.sf.ehcache.CacheManager}. * * @param cacheManager * the new cache manager */ public void setCacheManager(net.sf.ehcache.CacheManager cacheManager) { this.cacheManager = cacheManager; } /* (non-Javadoc) * @see org.springframework.cache.support.AbstractCacheManager#loadCaches() */ @Override protected Collection<Cache> loadCaches() { Assert.notNull(this.cacheManager, "A backing EhCache CacheManager is required"); Status status = this.cacheManager.getStatus(); Assert.isTrue(Status.STATUS_ALIVE.equals(status), "An 'alive' EhCache CacheManager is required - current cache is " + status.toString()); String[] names = this.cacheManager.getCacheNames(); Collection<Cache> caches = new LinkedHashSet<Cache>(names.length); for (String name : names) { caches.add(new LegendCache(this,this.cacheManager.getEhcache(name))); } return caches; } /* (non-Javadoc) * @see org.springframework.cache.support.AbstractCacheManager#getCache(java.lang.String) */ @Override public Cache getCache(String name) { Cache cache = super.getCache(name); if (cache == null) { // check the EhCache cache again // (in case the cache was added at runtime) Ehcache ehcache = this.cacheManager.getEhcache(name); if (ehcache != null) { cache = new LegendCache(this,ehcache); addCache(cache); } } return cache; } /* (non-Javadoc) * @see com.legendshop.business.cache.LegendCacheManager#isSupportQueryCache() */ @Override public boolean isSupportQueryCache() { return supportQueryCache; } /** * Sets the support query cache. * * @param supportQueryCache * the new support query cache */ public void setSupportQueryCache(boolean supportQueryCache) { this.supportQueryCache = supportQueryCache; } /* (non-Javadoc) * @see com.legendshop.business.cache.LegendCacheManager#getRelCacheName() */ @Override public String getRelCacheName() { return relCacheName; } /** * Sets the rel cache name. * * @param relCacheName * the new rel cache name */ public void setRelCacheName(String relCacheName) { this.relCacheName = relCacheName; } /* (non-Javadoc) * @see com.legendshop.business.cache.LegendCacheManager#isRemoveAllEntries() */ @Override public boolean isRemoveAllEntries() { return false; } /** * Sets the removes the all entries. * * @param removeAllEntries * the new removes the all entries */ public void setRemoveAllEntries(boolean removeAllEntries) { this.removeAllEntries = removeAllEntries; } } LegendCache:Ehcache的实现类 package com.legendshop.business.cache; import java.util.Collection; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import net.sf.ehcache.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.Cache; import org.springframework.cache.ehcache.EhCacheCache; import org.springframework.util.Assert; import com.legendshop.model.entity.BaseEntity; /** * The Class LegendCache. */ public class LegendCache implements Cache { /** The log. */ private final Logger log = LoggerFactory.getLogger(LegendCache.class); /** The cache manager. */ private final LegendCacheManager cacheManager; /** The cache. */ private final Ehcache cache; /** The SUFFIX. */ private final String SUFFIX = "List"; /** * Create an {@link EhCacheCache} instance. * * @param cacheManager * the cache manager * @param ehcache * backing Ehcache instance */ public LegendCache(LegendCacheManager cacheManager, Ehcache ehcache) { Assert.notNull(ehcache, "Ehcache must not be null"); Status status = ehcache.getStatus(); Assert.isTrue(Status.STATUS_ALIVE.equals(status), "An 'alive' Ehcache is required - current cache is " + status.toString()); this.cacheManager = cacheManager; this.cache = ehcache; } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#getName() */ @Override public String getName() { return this.cache.getName(); } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#getNativeCache() */ @Override public Ehcache getNativeCache() { return this.cache; } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#clear() */ @Override public void clear() { this.cache.removeAll(); } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#get(java.lang.Object) */ @Override public ValueWrapper get(Object key) { Element element = this.cache.get(key); if (element != null) { //log.info("get from cache {} by key {}, result {}", new Object[] { getName(), key, element.getObjectValue() }); return new LegendValueWrapper(element.getObjectValue()); } else { return null; } } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#put(java.lang.Object, * java.lang.Object) */ @Override public void put(Object key, Object value) { //log.info("put into cache {} by key {}, value {}", new Object[] { getName(), key, value }); this.cache.put(new Element(key, value)); if(this.getName().endsWith(SUFFIX) && cacheManager.isSupportQueryCache() && Collection.class.isAssignableFrom(value.getClass())){ //for list // 如果是列表,则保存以ID为主键的关系 Collection<BaseEntity> coll = (Collection<BaseEntity>) value; for (BaseEntity entity : coll) { //put relevant into relcache format: entity name + id:{[cacheName, key]} Cache relCache = cacheManager.getCache(cacheManager.getRelCacheName()); String relCacheKey = this.getName().substring(0, this.getName().length() - 4) + entity.getId(); LegendValueWrapper valueWrapper = (LegendValueWrapper) relCache.get(relCacheKey); if (valueWrapper == null) { valueWrapper = new LegendValueWrapper(entity.getId()); } if (valueWrapper.addRelObject(this.getName(),key)) { //log.info("put into rel cache {} by key {}, value {}", new Object[] {relCacheKey, relCacheKey, valueWrapper }); relCache.put(relCacheKey, valueWrapper); } } } } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#evict(java.lang.Object) */ @Override public void evict(Object key) { //evict effect only in entity cache //log.info("evict from cache {} by key {}", getName(), key); this.cache.remove(key); //for list if (cacheManager.isSupportQueryCache() && !this.getName().endsWith(SUFFIX)) { // clean entity Cache relCache = cacheManager.getCache(cacheManager.getRelCacheName()); if (relCache != null) { String relCacheKey = this.getName() + key; LegendValueWrapper valueWrapper = (LegendValueWrapper)relCache.get(relCacheKey); if(valueWrapper != null){ for (CacheNameAndItemWrapper warpper : valueWrapper.getRelObject()) { //log.info("evict from cache {} by key {}", warpper.getCacheName(), warpper.getKey()); cacheManager.getCache(warpper.getCacheName()).evict(warpper.getKey()); } } //remove relcache relCache.evict(relCacheKey); } }else if(cacheManager.isRemoveAllEntries()){ Cache listCache = cacheManager.getCache(this.getName() + SUFFIX); if(listCache != null){ listCache.clear(); } } } } LegendQueryCache:实现类似Hibernate的二级缓存机制,将ID list保存在List Cache中,多次和Entity交互取得全部的Entity,显然会影响一点性能。 package com.legendshop.business.cache; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import net.sf.ehcache.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.Cache; import org.springframework.cache.ehcache.EhCacheCache; import org.springframework.util.Assert; import com.legendshop.model.entity.BaseEntity; /** * The Class LegendCache. */ public class LegendQueryCache implements Cache { /** The log. */ private final Logger log = LoggerFactory.getLogger(LegendQueryCache.class); /** The cache manager. */ private final LegendCacheManager cacheManager; /** The cache. */ private final Ehcache cache; /** The SUFFIX. */ private final String SUFFIX = "List"; /** * Create an {@link EhCacheCache} instance. * * @param cacheManager * the cache manager * @param ehcache * backing Ehcache instance */ public LegendQueryCache(LegendCacheManager cacheManager, Ehcache ehcache) { Assert.notNull(ehcache, "Ehcache must not be null"); Status status = ehcache.getStatus(); Assert.isTrue(Status.STATUS_ALIVE.equals(status), "An 'alive' Ehcache is required - current cache is " + status.toString()); this.cacheManager = cacheManager; this.cache = ehcache; } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#getName() */ @Override public String getName() { return this.cache.getName(); } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#getNativeCache() */ @Override public Ehcache getNativeCache() { return this.cache; } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#clear() */ @Override public void clear() { this.cache.removeAll(); } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#get(java.lang.Object) */ @Override public ValueWrapper get(Object key) { Element element = this.cache.get(key); if (element != null) { Object value = element.getObjectValue(); if (cacheManager.isSupportQueryCache() && this.getName().endsWith(SUFFIX)&& Collection.class.isAssignableFrom(value.getClass())) { //get cache from entity cache by id list String entityCacheName = this.getName().substring(0, this.getName().length() - 4); Collection<Serializable> ids = (Collection<Serializable>)value; Collection<Object> result = new ArrayList<Object>(); if(ids == null){ return null; }else{ Cache entityCache = cacheManager.getCache(entityCacheName); for (Serializable id : ids) { ValueWrapper warpper = entityCache.get(id); if(warpper == null){ //if one of result is null then retrieve data again return null; } result.add(warpper.get()); } return new LegendValueWrapper(result); } }else{ log.info("get from cache {} by key {}, result {}", new Object[] { getName(), key, value }); return new LegendValueWrapper(value); } } else { return null; } } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#put(java.lang.Object, * java.lang.Object) */ @Override public void put(Object key, Object value) { if(this.getName().endsWith(SUFFIX) && cacheManager.isSupportQueryCache() && Collection.class.isAssignableFrom(value.getClass())){ //for list // 如果是列表,则保存以ID为主键的关系 Collection coll = (Collection) value; Collection<Serializable> entityId = new ArrayList<Serializable>(); for (Object entityObj : coll) { //put into entity cache for each row, format: entity id: entity String entityCacheName = this.getName().substring(0, this.getName().length() - 4); Cache entityCache = cacheManager.getCache(entityCacheName); BaseEntity entity = (BaseEntity)entityObj; entityId.add(entity.getId()); entityCache.put(entity.getId(), entityObj); //put relevant into relcache format: entity name + id:{[cacheName, key]} Cache relCache = cacheManager.getCache(cacheManager.getRelCacheName()); String relCacheKey = entityCacheName + entity.getId(); LegendValueWrapper valueWrapper = (LegendValueWrapper) relCache.get(relCacheKey); if (valueWrapper == null) { valueWrapper = new LegendValueWrapper(entity.getId()); } if (valueWrapper.addRelObject(this.getName(),key)) { log.info("put into cache {} by key {}, value {}", new Object[] {relCacheKey, entity.getId(), valueWrapper }); relCache.put(relCacheKey, valueWrapper); } } //put entity id list into cache cache.put(new Element(key, entityId)); }else{ //only one entity log.info("put into cache {} by key {}, value {}", new Object[] { getName(), key, value }); this.cache.put(new Element(key, value)); } } /* * (non-Javadoc) * * @see org.springframework.cache.Cache#evict(java.lang.Object) */ @Override public void evict(Object key) { //evict effect only in entity cache log.info("evict from cache {} by key {}", getName(), key); this.cache.remove(key); //for list if (cacheManager.isSupportQueryCache() && !this.getName().endsWith(SUFFIX)) { // clean entity Cache relCache = cacheManager.getCache(cacheManager.getRelCacheName()); if (relCache != null) { String relCacheKey = this.getName() + key; LegendValueWrapper valueWrapper = (LegendValueWrapper)relCache.get(relCacheKey); if(valueWrapper != null){ for (CacheNameAndItemWrapper warpper : valueWrapper.getRelObject()) { log.info("evict from cache {} by key {}", warpper.getCacheName(), warpper.getKey()); cacheManager.getCache(warpper.getCacheName()).evict(warpper.getKey()); } } //remove relcache relCache.evict(relCacheKey); } } } } StringKeyGenerator:需要改写key的生成策略 package com.legendshop.core.cache; import java.lang.reflect.Method; import org.springframework.cache.interceptor.KeyGenerator; /** * 实现新的key generator基于两点原因: * 1.memcached的键都是string; * 2.分布式环境不能用hashcode. * */ public class StringKeyGenerator implements KeyGenerator { /* (non-Javadoc) * @see org.springframework.cache.interceptor.KeyGenerator#generate(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) */ public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(50); sb.append(target.getClass().getName()); sb.append('.'); sb.append(method.getName()); sb.append('.'); for (Object o : params) { if(o != null){ sb.append(o.toString()); sb.append('.'); } } return sb.toString(); } } 另外需要在ehcache.xml中把Cache name定义好,否则CacheManager会找不到Cache。 使用查询缓存需要遵循Hibernate类似的规则,详细见ppt。 由于这些代码不是一个简单的Demo,各个功能分布于系统各个部分,如果需要看效果,则需要从svn把真个系统拿下来再运行,目前已经在Ehcache环境下测试通过。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-05-14
关键是怎么拓展spring现有的几个cache annotation,难道你要全部重写?
|
|
返回顶楼 | |
发表时间:2012-05-14
最后修改:2012-05-14
希望能快点上传~
能实现联动的无非就是 entity cache 和list cache了 |
|
返回顶楼 | |
发表时间:2012-05-14
2. 缓存越靠近系统前端,表现越好 : 即数据越接近于用户越好
|
|
返回顶楼 | |
发表时间:2012-05-14
aa87963014 写道 希望能快点上传~
能实现联动的无非就是 entity cache 和list cache了 annotation是重用spring的,@caching已经满足我们大部分情况了,内部cache如何划分就要看自己的设计了。 我们在cache的get和set入口开始做文章,当然用ehcache的cache event linstner也是可以实现的,但是就是不通用了。我们还要支持memcached的.我们还要支持memcached等其他的cache,因此跟逻辑相关的都要统一处理. |
|
返回顶楼 | |
发表时间:2012-05-14
1、Entity更新时从RelationShop找到对应的List并更新
这个不需要吧 因为你的List存的只是ID啊 2、RelationShop cache 以Entity ID 作为key, 记录引用到这个Entity的List的所在的cache名称和key,用于当Entity更新时找到他所影响的List一起做更新操作 建议上一个时间戳缓存,类似于Hibernate,我觉得要比RelationShop好。 3、当Entity Create Delete时应该直接删除List/修改时间戳吧? 4、你们现在 List如何保存呢? 如何得到Entity类型? 如果List为空呢? 几点疑问和见解 |
|
返回顶楼 | |
发表时间:2012-05-14
想了下,由entity的改变更新List 要加上很多限制,例如:list 保存的是 num>0的entity
现在其中一个entity 的num变为了0,那么list就是错误的结果。 这个需要使用者自己掂量怎么去维护关系 |
|
返回顶楼 | |
发表时间:2012-05-14
jinnianshilongnian 写道 1、Entity更新时从RelationShop找到对应的List并更新
这个不需要吧 因为你的List存的只是ID啊 2、RelationShop cache 以Entity ID 作为key, 记录引用到这个Entity的List的所在的cache名称和key,用于当Entity更新时找到他所影响的List一起做更新操作 建议上一个时间戳缓存,类似于Hibernate,我觉得要比RelationShop好。 3、当Entity Create Delete时应该直接删除List/修改时间戳吧? 4、你们现在 List如何保存呢? 如何得到Entity类型? 如果List为空呢? 几点疑问和见解 1。刚才提到了List cache有2种方案,1.放ID List, 2. 放整个List。比如这个List下的某个Entity更新或者删除了,那这个List就要作废了,需要重新从数据库拿记录。这2种方案在其关联的Entity改变之后,都要干掉List Cache 2。RelationShop cache 的数据结构是这样的, Entity ID:{[cachename1, key1],[cachename2, key2]...}, 不知道你说的时间戳是否ehcache的create time? 3. Entity Cache 更新时通过RelationShop cache找到他所影响的List Cache 并删除,跟着删除对应的RelationShop cache 4. Cache name就是一个Entity name, 每个Entity实现一个BadeEntity的接口,里面有一个getId()的方法需要实现,对Cache而言,我不需要知道Entity的类型,只要知道他的Id作为Key就好,value的类型是Object. List为空也是一种结果缓存下来的,只是他不受任何Entity的影响。 |
|
返回顶楼 | |
发表时间:2012-05-14
onecan 写道 jinnianshilongnian 写道 1、Entity更新时从RelationShop找到对应的List并更新
这个不需要吧 因为你的List存的只是ID啊 2、RelationShop cache 以Entity ID 作为key, 记录引用到这个Entity的List的所在的cache名称和key,用于当Entity更新时找到他所影响的List一起做更新操作 建议上一个时间戳缓存,类似于Hibernate,我觉得要比RelationShop好。 3、当Entity Create Delete时应该直接删除List/修改时间戳吧? 4、你们现在 List如何保存呢? 如何得到Entity类型? 如果List为空呢? 几点疑问和见解 1。刚才提到了List cache有2种方案,1.放ID List, 2. 放整个List。比如这个List下的某个Entity更新或者删除了,那这个List就要作废了,需要重新从数据库拿记录。这2种方案在其关联的Entity改变之后,都要干掉List Cache 2。RelationShop cache 的数据结构是这样的, Entity ID:{[cachename1, key1],[cachename2, key2]...}, 不知道你说的时间戳是否ehcache的create time? 3. Entity Cache 更新时通过RelationShop cache找到他所影响的List Cache 并删除,跟着删除对应的RelationShop cache 4. Cache name就是一个Entity name, 每个Entity实现一个BadeEntity的接口,里面有一个getId()的方法需要实现,对Cache而言,我不需要知道Entity的类型,只要知道他的Id作为Key就好,value的类型是Object. List为空也是一种结果缓存下来的,只是他不受任何Entity的影响。 2。RelationShop cache 的数据结构是这样的, Entity ID:{[cachename1, key1],[cachename2, key2]...}, 不知道你说的时间戳是否ehcache的create time? 不是create time 是修改的时间。 hibernate也是采用这种时间戳方式 比如你有个Entity 有一个 如 s属性 原来为1 后来修改成 2 但你有一个查询 条件为s>1 这样你的List可能有几个失效的。 这个你的场景会遇到吗? |
|
返回顶楼 | |
发表时间:2012-05-14
aa87963014 写道 想了下,由entity的改变更新List 要加上很多限制,例如:list 保存的是 num>0的entity
现在其中一个entity 的num变为了0,那么list就是错误的结果。 这个需要使用者自己掂量怎么去维护关系 那这个List就等于失效了,你可以重新填充Entity Cache并找到他对应的List Cache也把新的数据填充在里面,不过我建议是直接remove掉更方便了。这种方案只是影响部分List Cache,提高了实时性,要比一有改动就把List Cache整个干掉来的划算。而且在我们程序中,我们可以配置成任何一个Entity改动,其对应的List Cache全部干掉,这个Spring cache默认就支持了,另外我们也可以不开启这种模式,那等于普通的Spring cache了,所以灵活性是掌握在我们手里的. |
|
返回顶楼 | |