缓存组件(qiluCache)介绍
缓存组件(qiluCache)介绍
spring-cache提供了很好的调用接口,对于开发人员在使用cache的时候,可以做到很简单.但是spring-cache有下面几点做的很不好,而且在短时间内,看不到spring有优化的可能
- 没有提供过期时间设置,这是一个很严重的问题
- 没有针对cache的防穿透处理
qilu-cache仿照spring-cache的api,支持上述2种场景,缓存数据存储使用fastjson做数据序列化.和一般的cache封装不太一样,除了提供原始介质的set,get操作,还提供cacheService的高级封装操作,可以有效避免缓存穿透.
代码集成
maven引入
<dependency>
<groupId>com.9istock.base</groupId>
<artifactId>qilu-cache-ehcache</artifactId>
<version>1.0.0</version>
</dependency>代码调用
spring配置
<!-- 使用baseCache的api -->
<bean name="baseCacheManager" class="com.istock.base.qiluCache.map.ConcurrentMapCacheManager">
</bean>
<cache:baseCache cacheManager="baseCacheManager"/>java调用
查询使用缓存
缓存的key是spEL表达式,所以需要使用#开头,支持从接口参数中获取 expireTime是过期时长,单位:秒
//当前代码的含义是,在执行queryById的方法之前,首先把传入参数id的值作为cacke的key,去cache中查询是否存在对应的数据,如果存在则不再执行内部的方法逻辑直接返回
//如果cache中不存在对应id的值,则执行方法体,当方法正常返回后,将返回值设置到cache里,cache的key使用入参id的值
@UseCache(key="'senvon1'+#id", expireTime = 30*60)
public TestObject queryById(Long id){
TestObject result = new TestObject();
result.setAge(11);
result.setName("senvon");
TestObject child = new TestObject();
child.setAge(12);
child.setName("senvon child");
result.getChildren().add(child);
count++;
return result;
}直接删除缓存
//在执行方法前,删除cacheKey为id的缓存
@DeleteCache(key="'senvon1'+#id")
public void deleteById(Long id){
}更新缓存
//先执行方法体
//当方法执行返回后,更新cachekey为id的缓存,更新内容就是返回的内容
@UpdateCache(key="'senvon1'+#id")
public TestObject updateById(Long id , TestObject obj){
return obj;
}手动调用缓存
提供cacheService的高级缓存操作接口,将get和set操作进行有效结合,避免缓存穿透.具体使用方式如下:
XML配置
<bean name="cacheService" class="com.istock.base.qiluCache.CacheService">
<property name="cacheManager" ref="baseCacheManager"></property>
</bean>api调用
cacheService.findObject("RECORD_USER_KEY"+ssoUser.getUserId(), new TypeReference<Integer>() {}, 10L, new CacheLoadCallback<Integer>() {
@Override
public Integer loadCache() {
aschTaskService.userOnlineRecordUpdate(ssoUser.getToken(), ssoUser.getCurrentDept() != null ? ssoUser.getCurrentDept().getDeptCode():null);
return 11;
}
});cacheService不但可以作为缓存的防穿透方法使用,还能作为一种缓存锁机制而使用.
注意
请各位仔细看看上面的代码,这其实是一段缓存锁操作,return 11返回的是一个固定的值,这段话的意思是,去缓存中取一个key为RECORD_USERKKEY+userId的值,如果不存在,就调用CacheLoadCallback的loadCache方法,如果这段代码一直被调用,会有10秒的时间,不会执行loadCache内的方法,至于cache的内容,并不是很重要.
缓存锁的使用
在1.1版本以后,加入通用缓存锁机制.
缓存锁一共提供如下几个方法
/**尝试获得一个缓存锁
* 锁一次,如果锁成功,返回一个锁的id,后续可以根据id解锁
*
* @param lockKey 锁的key
* @param lockTimeSec 锁的最大时间,超过时间会自动释放
* @return 如果返回的!=null,则代表锁成功,返回值为锁的id
* 否则,锁失败
*/
public String lock(String lockKey, Long lockTimeSec)
/**尝试获得一个缓存锁
* 有获得锁的等待时间,中间可以多次获得锁,直到锁等待时间超时
*
* @param lockKey 锁的key
* @param waitSecond 获得锁的等待时间
* @param lockTimeSec 锁的最大时间,超过时间会自动释放
* @return 如果返回的!=null,则代表锁成功,返回值为锁的id
* 否则,锁失败
* 该方法不合适和releaseLock同时调用
*/
public String lock(String lockKey,Long lockWaitSec , Long lockTimeSec)
/**释放一个锁
* 如果传入lockIdentify,就会检查identify,检查不通过,则无法解锁
* 如果没有传入lockIdentify,则会删除缓存key,达到解锁目的
* 不传入lockIdentify的解锁方式,会造成不是当前线程的锁,也可以解开,从而造成已加锁的线程又会没有锁,会被其他线程再次加锁
* @param lockKey
*/
public void releaseLock(String lockKey , String lockIdentify)
/**一个简单的锁操作
* 执行一次锁操作,如果能获得锁,调用lockCallback方法,如果不能获得锁,什么都不会执行.
* @param lockKey 锁的key
* @param lockTimeSec 锁保护时长,如果callback中的执行过程超过lockTimeSec,则锁会失效,但当前调用还是安全的,造成的后果是有2个线程同时执行lockcallback
* @param callback 在获得锁的情况下,进行安全操作
*/
public void lockCallback(String lockKey , Long lockTimeSec , LockCallback callback)锁的使用分析
缓存锁只能实现在相同时间发起的调用,进行排斥操作.这种排斥操作有一定的危险性.
虽然lockService.lockCallback提供了一种安全的锁操作,但是如何运用不得当,一样无法实现锁.
假设调用的代码是如下编写的
lockService.lockCallback("key" , 10L , new LockCallback(){});情况一,有2个线程同时发起,一定会有一个线程会被阻断,无法获得锁.
情况二,一个线程率先发起请求,另外一个线程在10ms后也发起请求,而lockCallback的执行内容只有5ms,这时候前面一个线程会获得锁,执行lockCallback,最后解锁,第二个线程也获得锁,也会执行lockCallback,无法起到锁的效果,定时发起的任务,统一机器时间是很重要的操作.
lockService还提供了单独的lock操作和releaseLock的操作,如果操作不得法,也是会出现锁出问题的情况
假设调用的情形如下
String lockId = lockService.lock("key",10L,10L);
if(lockId != null){
//获得锁
//doInLock
//解锁
lockService.releaseLock("key",lockId);
}上述的调用初看上去,是没问题的,实际运行是可能存在问题.
假设有A,B 2个线程同一个时间发起,A线程第一次调用锁的时候就获得了锁,马上执行doInLock,另外一个B线程在慢慢等待.如果A线程在doInLock里面执行很快,10ms就执行完了,删除可key的所有锁,B线程还没有超过等待时间,就会再次获得锁,执行doInLock,再次释放锁.
提示
public String lock(String lockKey,Long lockWaitSec , Long lockTimeSec) 不适合手动调用解锁
缓存的使用过程存在一定的危险性,请谨慎使用.
