@yanglfyangl
2018-05-15T07:56:49.000000Z
字数 3488
阅读 665
- 在可能有错误的情况下使用缓存
- 随意定义缓存过期时间
- 函数体内有必须要调用的逻辑的情况下,使用了缓存。
- 缓存使用时必须基于性能测试,如果没有性能问题的情况下,不要轻易使用接口缓存。(未来大家熟悉了逻辑后再说)
- 缓存的过期时间必须经过“业务需求”层面的同意。随意设置过期时间是不允许的。
- 对于大接口(代码行数比较多,逻辑比较复杂的),不能轻易使用缓存。将其有效拆分后合理加入缓存。
@Cached(name="topic-", key="#req.ID", expire = 3600)
public Response<List<TopicsMetaData>> listTopicsByKeyWord(Request<Map<String, Object>> req) {
Response<List<TopicsMetaData>> res = Response.create();
res.setSid(req.getSid());
try {
List<TopicsMetaData> dto = topicServiceImpl.listTopicsByKeyWord(req.getData());
res.success(dto);
} catch (Exception e) {
logger.error(e.toString());
res.setErrorMsg("请联系管理员");
res.setSuccess(false);
}
return res;
}
这样使用缓存很容易产生这样的问题
- 如果listTopicsByKeyWord出现异常,我们返回的错误值被缓存下来,而且1个小时内根本不会过期。这时候系统是处于严重问题之中的。
这种情况下如何解决呢?
@Cached(name="topic-", key="#Key", expire = 600)
private List<> listTopicsByKeyWord(String Key){
return topicServiceImpl.listTopicsByKeyWord(req.getData());
}
public Response<List<TopicsMetaData>> listTopicsByKeyWord(Request<Map<String, Object>> req) {
Response<List<TopicsMetaData>> res = Response.create();
res.setSid(req.getSid());
try {
List<TopicsMetaData> dto = topicServiceImpl.listTopicsByKeyWord(req.getData());
res.success(dto);
} catch (Exception e) {
logger.error(e.toString());
res.setErrorMsg("请联系管理员");
res.setSuccess(false);
}
return res;
}
这样,确保你的缓存注解所在的函数能够尽可能的缓存可以缓存的数据。
@Cached(name="topic-", key="#Key", expire = 600)
List<T> getList(String Key){
return ServiceA.getList(key)
}
上面的代码的一个问题是,如果系统因为一时的“小”问题,可能会导致未来的600秒缓存里的数据全是null值。
为了避免这个问题,可以将代码写成如下模式
模式A.
@Cached(name="topic-", key="#Key", expire = 600,cacheNullValue = false)
@CachePenetrationProtect
List<T> getList(String Key){
return ServiceA.getList(key)
}
@CachePenetrationProtect 注解保证当缓存未命中的时候,一个JVM里面只有一个线程去执行方法,其它线程等待结果。
模式B.
当然,如果缓存本身的过期时间比较短,也可以采用如下模式
@Cached(name="topic-", key="#Key", expire = 30,cacheNullValue = true)
List<T> getList(String Key){
return ServiceA.getList(key)
}
这样,用空值来进行一段时间的保证,一旦“异常“修复了,系统可以很快的恢复过来。
@Cached(name="topic-", key="#topicID", expire = 3600)
TopicInfo readTopic(String topicID){
TopicInfo info = ServiceA.getTopicInfo(key);
ServiceB.MarkTopicBeenReaded(info.ID);
ServiceC.IncreaseTopicValue(info.ID);
}
这个代码最严重的问题是,在同一个topic被读的时候,MarkTopicBeenReaded和IncreaseTopicValue只有很有限的机会被调用。这样的方法必须合理的拆解
@Cached(name="topic-", key="#topicID", expire = 3600)
TopicInfo getTopic(String topicID){
return ServiceA.getTopicInfo(key);
}
TopicInfo readTopic(String topicID){
TopicInfo info = getTopic(topicID);
ServiceB.MarkTopicBeenReaded(info.ID);
ServiceC.IncreaseTopicValue(info.ID);
}
@Cached(name="topic-", key="#topicID", expire = 3600)
@CacheRefresh(timeUnit = TimeUnit.MINUTES, refresh = 60)
TopicInfo getTopic(String topicID){
return ServiceA.getTopicInfo(key);
}
上面的代码很可能性能很高,但这时候的数据一致性可能出现很多问题,最好是:能避免先避免(后期性能要求更高,同时大家对于业务更熟悉后,再根据情况考虑)。
比如如下接口,接口内已经封装了返回数据和状态码的情况
@Cached(name="topic-", key="#var1.data.id", expire = 3600)
Response<CircleDetailsVo> getCircleDetails(Request<CircleDto> var1);
这种情况下,使用缓存就会有我们上面提到的风险。
- 返回值如果是异常值的话,异常值可能被缓存了,不符合要求。
- 缓存穿透保护很难处理。
对这样的接口,原则上
- 业务端不轻易加缓存。
- 如果加缓存优先走”业务缓存模式“,而不是jetcache注解模式。比如我们目前有的:User, Circle, Crowd三个缓存方法类。
如果最后我们还需要jetcache进行一定的优化来让我们调用引擎更快,则处理方式是:
//接口
Response<CircleDetailsVo> getCircleDetails(Request<CircleDto> var1);
//调用引擎的实现。
@Cached(name="topic-", key="#data.id", expire = 3600)
CircleDetailsVo getCircleDetails(CircleDto data){
Response<CircleDetailsVo> res = getCircleDetails(...);
if(res.code == '200' && res.data !=null && ...) {
return res.data;
} else {
//可能有两种可能性
//1. 通过异常返回数据。
throw new RespondException("", res);
//2. 如果返回空值是合理的,则
return null;
}
}
当然,到底是使用异常返回还是使用空值返回,需要根据业务情况case by case.