2014-09-24 10 views
5

Sto usando Spring + Redis come componente della cache nel nuovo progetto. Il file di configurazione XML primavera è:Handle di errore Redis Spring

<!-- Jedis Connection --> 
<bean id="jedisConnectionFactory" 
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" 
    p:host-name="${redis.ip}" p:port="${redis.port}" p:use-pool="${redis.use-pool}" /> 

<!-- Redis Template --> 
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> 
    <property name="connectionFactory" ref="jedisConnectionFactory" /> 
    <property name="keySerializer"> 
     <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> 
    </property> 
    <property name="valueSerializer"> 
     <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> 
    </property> 
</bean> 

<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:template-ref="redisTemplate"/> 

<cache:annotation-driven mode="proxy" proxy-target-class="true" cache-manager="cacheManager" /> 

L'utilizzo è

@Cacheable(value = "cacheManager", key="#userId") 
public User getUser(String userId) { 
    System.out.println("execute=="); 
    return userAdminMapper.getUser(userId); 
} 

Il mio banco di prova è:

@Test 
public void testCacheUser2() { 
    String id = "test"; 
    User user = userService.getUser(id); 
    System.out.println(user); 
    user.setUserCreateDate(new Date()); 
    userService.updateUser(user); 
    User user2 = userService.getUser(id); 
    System.out.println(user2); 
    User user3 = userService.getUser(id); 
    System.out.println(user3); 
} 

Se il server Redis è in esecuzione, il codice viene eseguito correttamente. Ma la mia domanda è se l'arresto del server Redis, si getterà eccezione:

org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect 
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:140) 
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:229) 
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:57) 
    at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128) 
    at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91) 
    at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78) 
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:177) 
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152) 
    at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:87) 
    at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:297) 
    at org.springframework.cache.interceptor.CacheAspectSupport.findInAnyCaches(CacheAspectSupport.java:287) 
    at org.springframework.cache.interceptor.CacheAspectSupport.collectPutRequests(CacheAspectSupport.java:266) 
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:199) 
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:178) 
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:60) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644) 
    at sg.infolab.common.admin.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$c7f982a7.getUser(<generated>) 
    at sg.infolab.admin.test.RedisServiceTest.testCacheUser2(RedisServiceTest.java:35) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) 
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83) 
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175) 
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect 
    at redis.clients.jedis.Connection.connect(Connection.java:150) 
    at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:71) 
    at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1783) 
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:137) 
    ... 50 more 
Caused by: java.net.ConnectException: Connection refused: connect 
    at java.net.PlainSocketImpl.socketConnect(Native Method) 
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351) 
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213) 
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200) 
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) 
    at java.net.Socket.connect(Socket.java:529) 
    at redis.clients.jedis.Connection.connect(Connection.java:144) 
    ... 53 more 

voglio chiedere se il cliente non potrebbe collegarsi Redis Server, il motivo per cui si è buttare eccezione? Possiamo configurare lo scenario come questo: se il livello della cache (Redis Server) non riesce a connettersi (forse è in crash o la rete non è attiva), dovrebbe connettersi direttamente al database e recuperare i dati.

risposta

12

Ho avuto lo stesso problema. Sto sviluppando alcuni servizi dati rispetto a un database, utilizzando Redis come archivio cache tramite le annotazioni Spring Caching. Se il server Redis non è più disponibile, voglio che i servizi continuino a funzionare come se fossero non collegati, piuttosto che generare eccezioni.

Inizialmente ho provato un CacheErrorHandler personalizzato, un meccanismo fornito da Spring. Non ha funzionato abbastanza, perché gestisce solo RuntimeExceptions, e lascia comunque che cose come java.net.ConnectException faccia esplodere le cose.

Alla fine ciò che ho fatto è estendere RedisTemplate, sovrascrivendo alcuni metodi execute() in modo che registrino gli avvisi anziché propagare le eccezioni. Sembra un po 'un trucco, e potrei aver ignorato troppo pochi metodi execute() o troppi, ma funziona come un incantesimo in tutti i miei casi di test.

C'è un aspetto operativo importante per questo approccio, però. Se il server Redis non è disponibile, è necessario svuotarlo (pulire le voci) prima di renderlo nuovamente disponibile. In caso contrario, è possibile che si inizi a recuperare le voci della cache con dati errati a causa degli aggiornamenti verificatisi nel frattempo.

Di seguito è la fonte. Sentiti libero di usarlo. Spero possa essere d'aiuto.

import java.util.List; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.data.redis.core.RedisCallback; 
import org.springframework.data.redis.core.RedisTemplate; 
import org.springframework.data.redis.core.SessionCallback; 
import org.springframework.data.redis.core.script.RedisScript; 
import org.springframework.data.redis.serializer.RedisSerializer; 


/** 
* An extension of RedisTemplate that logs exceptions instead of letting them propagate. 
* If the Redis server is unavailable, cache operations are always a "miss" and data is fetched from the database. 
*/ 
public class LoggingRedisTemplate<K, V> extends RedisTemplate<K, V> { 

    private static final Logger logger = LoggerFactory.getLogger(LoggingRedisTemplate.class); 


    @Override 
    public <T> T execute(final RedisCallback<T> action, final boolean exposeConnection, final boolean pipeline) { 
     try { 
      return super.execute(action, exposeConnection, pipeline); 
     } 
     catch(final Throwable t) { 
      logger.warn("Error executing cache operation: {}", t.getMessage()); 
      return null; 
     } 
    } 


    @Override 
    public <T> T execute(final RedisScript<T> script, final List<K> keys, final Object... args) { 
     try { 
      return super.execute(script, keys, args); 
     } 
     catch(final Throwable t) { 
      logger.warn("Error executing cache operation: {}", t.getMessage()); 
      return null; 
     } 
    } 


    @Override 
    public <T> T execute(final RedisScript<T> script, final RedisSerializer<?> argsSerializer, final RedisSerializer<T> resultSerializer, final List<K> keys, final Object... args) { 
     try { 
      return super.execute(script, argsSerializer, resultSerializer, keys, args); 
     } 
     catch(final Throwable t) { 
      logger.warn("Error executing cache operation: {}", t.getMessage()); 
      return null; 
     } 
    } 


    @Override 
    public <T> T execute(final SessionCallback<T> session) { 
     try { 
      return super.execute(session); 
     } 
     catch(final Throwable t) { 
      logger.warn("Error executing cache operation: {}", t.getMessage()); 
      return null; 
     } 
    } 
} 
+0

Questo potrebbe essere una caratteristica piacevole di aver rotolato in primavera-dati. Qualcuno ha aperto un biglietto o una PR per discuterne con la squadra? –

+0

Grazie per ciò, @KevinCrowell. Potrei metterlo fuori come un progetto a sé stante perché, a posteriori, potrebbe essere necessario un miglioramento. Il codice precedente presume che Redis era completamente inattivo, e non attivato, e quindi sarebbe vuoto quando sarà disponibile. Nel caso in cui Redis fosse solo temporaneamente irraggiungibile, o se Redis fosse andato giù ma le cache fossero persistenti, gli aggiornamenti al database sarebbero comunque passati e le voci della cache potrebbero essere stantie. Forse dovrebbe "ricordare" i codici cache ricevuti mentre Redis non era disponibile, e sfrattarli quando diventa disponibile Redis. – RichW

0

Ho lo stesso errore. E sono riuscito a risolvere con l'aggiunta di due cose:

  • timeout per ConnectionFactory
  • gestore degli errori
@Configuration 
@ConditionalOnProperty(name = "redis.enabled", havingValue = "true") 
@EnableCaching 
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer { 

    @Value("${redis.host}") 
    private String host; 

    @Value("${redis.port}") 
    private Integer port; 

    @Value("${redis.expiration.timeout}") 
    private Integer expirationTimeout; 

    @Bean 
    public JedisConnectionFactory redisConnectionFactory() { 
     JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(); 
     redisConnectionFactory.setHostName(host); 
     redisConnectionFactory.setPort(port); 
     redisConnectionFactory.setTimeout(10); 
     return redisConnectionFactory; 
    } 

    @Bean 
    public RedisTemplate<String, Set<String>> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) { 
     RedisTemplate<String, Set<String>> redisTemplate = new RedisTemplate<>(); 
     redisTemplate.setConnectionFactory(redisConnectionFactory); 
     return redisTemplate; 
    } 

    @Bean 
    public CacheManager cacheManager(@Autowired RedisTemplate redisTemplate) { 
     RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); 
     cacheManager.setDefaultExpiration(expirationTimeout); 
     return cacheManager; 
    } 

    @Override 
    public CacheErrorHandler errorHandler() { 
     return new RedisCacheErrorHandler(); 
    } 

    @Slf4j 
    public static class RedisCacheErrorHandler implements CacheErrorHandler { 

     @Override 
     public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { 
      log.info("Unable to get from cache " + cache.getName() + " : " + exception.getMessage()); 
     } 

     @Override 
     public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { 
      log.info("Unable to put into cache " + cache.getName() + " : " + exception.getMessage()); 
     } 

     @Override 
     public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { 
      log.info("Unable to evict from cache " + cache.getName() + " : " + exception.getMessage()); 
     } 

     @Override 
     public void handleCacheClearError(RuntimeException exception, Cache cache) { 
      log.info("Unable to clean cache " + cache.getName() + " : " + exception.getMessage()); 
     } 
    } 
} 
Problemi correlati