Отключить redis при большом количестве тайм-аутов с помощью spring boot

#spring-boot #redis

#spring-boot #redis

Вопрос:

У меня есть приложение, развернутое в AWS EC2, некоторое время (редко), я не могу подключиться и выполнить какую-либо команду в redis, я расследую основную причину этой проблемы.

Я использую Spring boot Redis (Elasticcache).

Я использую оболочку для перехвата любого исключения для продолжения процесса запроса.

Моя оболочка:

 class RedisCacheWrapper implements Cache {

private final Cache delegate;

public RedisCacheWrapper(Cache redisCache) {
    Assert.notNull(redisCache, "delegate cache must not be null");
    this.delegate = redisCache;
}

@Override
public String getName() {
    try {
        return delegate.getName();
    } catch (Exception e) {
        return handleException(e);
    }
}

@Override
public Object getNativeCache() {
    try {
        return delegate.getNativeCache();
    } catch (Exception e) {
        return handleException(e);
    }
}

@Override
public ValueWrapper get(Object key) {
    try {
        return delegate.get(key);
    } catch (Exception e) {
        return handleException(e);
    }
}

@Override
public <T> T get(Object o, Class<T> type) {
    try {
        return delegate.get(o, type);
    } catch (Exception e) {
        return handleException(e);
    }
}

@Override
public <T> T get(Object o, Callable<T> callable) {
    try {
        return delegate.get(o, callable);
    } catch (Exception e) {
        return handleException(e);
    }
}

@Override
public void put(Object key, Object value) {
    try {
        delegate.put(key, value);
    } catch (Exception e) {
        handleException(e);
    }
}

@Override
public ValueWrapper putIfAbsent(Object o, Object o1) {
    try {
        return delegate.putIfAbsent(o, o1);
    } catch (Exception e) {
        return handleException(e);
    }
}

@Override
public void evict(Object o) {
    try {
        delegate.evict(o);
    } catch (Exception e) {
        handleException(e);
    }
}

@Override
public void clear() {
    try {
        delegate.clear();
    } catch (Exception e) {
        handleException(e);
    }
}

private <T> T handleException(Exception e) {
    log.error("handleException", e);
    return null;
}}
 

В моей конфигурации redis я установил время ожидания равным 1 сек. Итак, когда connect / command не выполняется через 1 секунду, redis выдает исключение, подобное этому:

 Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out
 

Мои сомнения:
Есть хороший способ временно отключить кэш (без какого-либо развертывания), в то время как redis не подходит? Например: использование прерывания цепи?

Я думаю, сделайте это:

     @Cacheable()
    myMethodCached(){
       myRealMethod();
    }

    myRealMethod(){}
 

Поместите «myMethodCached» в команду HystrixCommand, если время ожидания истекло, то резервный метод выполняется без использования redis.

Проблема с этим подходом заключается в том, что мне нужно создать «запасной вариант» для всех методов, использующих кеш, я хотел бы «отключить» глобально (весь кеш будет пропущен).

Есть ли хорошее решение для «отключения» redis на определенный период?

Ответ №1:

Если вы используете Spring Data Redis, вы можете использовать поддержку Spring для обработки этих временных сбоев и исключений с помощью пользовательского обработчика исключений.

Код:

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 

Рекомендуем установить тайм-аут ниже, чем по умолчанию (60000):

 spring.cache.type=redis
spring.redis.timeout=100
 

Затем создайте пользовательский обработчик ошибок в контексте Spring:

 import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Configuration;

@Slf4j
@EnableCaching
@Configuration
public class CacheConfiguration extends CachingConfigurerSupport {

    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.info("Failure getting from cache: "   cache.getName()   ", exception: "   exception.toString());
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.info("Failure putting into cache: "   cache.getName()   ", exception: "   exception.toString());
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.info("Failure evicting from cache: "   cache.getName()   ", exception: "   exception.toString());
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.info("Failure clearing cache: "   cache.getName()   ", exception: "   exception.toString());
            }
        };
    }

}
 

Spring должен обнаружить сбой через 100 миллисекунд и выполнить резервное копирование для извлечения данных, полученных с помощью @Cacheable аннотированных методов, как если бы был промах кэша. И всякий раз, когда кэш восстанавливается, Spring снова начнет извлекать данные из кэша.

Комментарии:

1. у меня работало с использованием spring boot 2.6.4

2. Протестировано spring-boot@2.6.6 и работает хорошо, но только если экземпляр Redis все еще доступен. Если вы не можете подключиться к нему, клиент Lettuce будет жаловаться, и вызовы будут отключены (с io.lettuce.core.RedisCommandTimeoutException выдачей исключения). В этом случае обработчик ошибок никогда не достигается, что является позором, потому что это означает, что у вас должен быть другой запасной вариант на случай, если экземпляр полностью недоступен.

3. К сожалению, проблема, с которой я столкнулся, заключалась в том, что у меня также были Spring sessions ( spring-session-data-redis ), сохраненные в экземпляре Redis.