为了能够满足高并发、快速响应的需求,缓存系统在应用中扮演者越来越重要的作用,它能够减少应用与数据库的交互次数,进而降低数据库的压力。在java应用中使用缓存,很多开发人员会选择使用Spring提供的Cache组件,它提供了抽象的缓存模型,业务程序中只需要添加一个统一的注解,而后端可以通过配置切换不同的缓存系统/组件。
想象这样一个场景,由于突然的网络不稳定,Redis缓存系统出现了暂时性无法连接,而我们的业务系统又需要继续的提供服务;另一个场景,我们在自己的笔记本上想测试一个功能,但是暂时没有一个可使用的缓存系统。这两个场景下,我们都会看到系统会抛出这样的异常而不能提供服务:
org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:204)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:348)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:129)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:194)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:157)
at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:172)
at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:133)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:71)
at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:537)
at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:503)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:389)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:327)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
其实如果不是有极限的性能要求,缓存的暂时不可用不应该使得我们的应用系统不能提供服务,完全可以先直接走数据库,等到可用后再使用缓存。而Spring Cache是给我们提供了这种切入点做这种控制处理的。
新增一个Cache配置类,继承CachingConfigurerSupport类,实现errorHandler()方法,我们可以覆盖Spring默认的DefaultErrorHandler。为了保证数据一致性,缓存清除方法出错时,我们按原样抛出异常,在查询和插入缓存时如果出现了Redis连接的异常,不做异常抛出处理,这样会继续从数据库中读取/写入数据,不影响正常业务服务,等缓存连接恢复后,又可以正常的提供服务了。
@Configuration
@EnableCaching
public class AppCacheConfigurer extends CachingConfigurerSupport {
private Logger logger = LoggerFactory.getLogger(AppCacheConfigurer.class);
public class AppCacheErrorHandler implements CacheErrorHandler{
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object o) {
if(e instanceof JedisConnectionException || e instanceof RedisConnectionFailureException){
logger.warn("redis has lose connection:",e);
return;
}
throw e;
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object o, Object o1) {
if(e instanceof JedisConnectionException || e instanceof RedisConnectionFailureException){
logger.warn("redis has lose connection:",e);
return;
}
throw e;
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object o) {
throw e;
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
throw e;
}
}
@Override
public CacheErrorHandler errorHandler() {
return new AppCacheErrorHandler();
}
}
另外,如果长时间缓存不可用,有可能请求量太大对数据库造成冲击,这里我们也可以借鉴Neflix的Hystrix断路器的思想,设定一个阈值,如果连接失败次数超过这个值,后面的连接就直接抛错了,待连接恢复后,再重置这个值。