2014-09-24 11 views
5

Używam Spring + Redis jako komponentu pamięci podręcznej w nowym projekcie. Sprężyna config xml Plik:Błąd Spring Redis

<!-- 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" /> 

Użycie jest

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

Mój przypadek testowy jest:

@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); 
} 

Jeśli serwer Redis działa, kod działa poprawnie. Ale moje pytanie brzmi, czy wyłączyć serwer Redis, to rzucić wyjątek:

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 

Chcę zapytać, czy klient nie mógł połączyć Redis Server, dlaczego to będzie rzucić wyjątek? Czy możemy tak skonfigurować scenariusz - jeśli warstwa pamięci podręcznej (serwer Redis) nie może się połączyć (może się zawiesić lub sieć nie działa), powinna bezpośrednio łączyć się z bazą danych i pobierać dane.

Odpowiedz

12

Miałem ten sam problem. Zajmuję się tworzeniem niektórych usług danych na bazie danych, używając Redis jako magazynu pamięci podręcznej za pomocą adnotacji Spring Caching. Jeśli serwer Redis staje się niedostępny, chcę, aby usługi nadal działały tak, jak gdyby były bez pamięci, zamiast rzucać wyjątki.

Najpierw spróbowałem niestandardowego CacheErrorHandler, mechanizmu dostarczonego przez Spring. Nie działało, ponieważ obsługuje tylko Wyjątki RuntimeException i nadal pozwala na rzeczy takie jak java.net.ConnectException wysadzić w powietrze.

W końcu to, co zrobiłem, to rozszerzenie RedisTemplate, nadpisanie kilku metod execute(), tak aby rejestrowały ostrzeżenia zamiast propagować wyjątki. Wydaje się, że to trochę hackowanie i mogłem nadpisać zbyt mało metod execute() lub zbyt wiele, ale działa to jak urok we wszystkich moich testowych przypadkach.

Istnieje jednak ważny aspekt operacyjny tego podejścia. Jeśli serwer Redis staje się niedostępny, należy go opróżnić (wyczyścić wpisy) przed ponownym udostępnieniem. W przeciwnym razie istnieje możliwość, że zaczniesz pobierać wpisy pamięci podręcznej z niepoprawnymi danymi z powodu aktualizacji, które miały miejsce w międzyczasie.

Poniżej znajduje się źródło. Zapraszam do korzystania z niego. Mam nadzieję, że to pomoże.

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

To może być fajnym dodatkiem do już walcowane na wiosenno-danych. Czy ktoś otworzył bilet lub PR, aby omówić to z zespołem? –

+0

Dzięki za to, @KevinCrowell. Mogę to wydać jako własny projekt, ponieważ z perspektywy czasu może wymagać ulepszenia. Powyższy kod zakłada, że ​​Redis był całkowicie wyłączony i nie miał dostępu do niego, a zatem byłby pusty, gdy stanie się dostępny. Gdyby Redis był tylko chwilowo nieosiągalny, lub gdyby Redis przestał działać, ale pamięci podręczne były przechowywane, aktualizacje bazy danych nadal by działały, a wpisy pamięci podręcznej mogły być nieaktualne. Być może powinien "zapamiętać" klucze z pamięci podręcznej odebrane, gdy Redis był niedostępny, i eksmitować je, gdy Redis stanie się dostępny. – RichW

0

Mam ten sam błąd. I udało mi się go rozwiązać poprzez dodanie dwóch rzeczy:

  • limitu czasu ConnectionFactory
  • błędu obsługi
@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()); 
     } 
    } 
} 
Powiązane problemy