복붙노트

[REDIS] 레디 스 연결은 어떻게 해제 레디 스 캐싱에 대한 런타임에 실패했을 경우

REDIS

레디 스 연결은 어떻게 해제 레디 스 캐싱에 대한 런타임에 실패했을 경우

우리는 나머지 API는 응용 프로그램이 있습니다. 우리는 API 응답 캐싱 및 내부 방법 캐싱 레디 스를 사용합니다. 레디 스 연결은 우리의 API 아래로 제작됩니다. 우리는 원하는 레디 스 그 레디 스 연결이 실패 할 경우 캐시 또는 예외 대신에 우리의 API의 다운을 우회. 거기에 인터페이스 CacheErrorHandler는하지만하지 연결 문제 레디 스 레디 스 세트 작동 오류를 얻을 처리합니다. 우리는 봄 4.1.2를 사용하고 있습니다.

해결법

  1. ==============================

    1.조금 실망의 종기이. (레디 스 구현) 귀하의 응용 프로그램에서 사용하는 캐싱. 레디 스 연결 / 오래된 폐쇄하거나 경우에, 당신은 바이 패스 캐싱 및 (아마도) (예를 들어, RDBMS)을 기본 데이터 저장소로 직접 이동에 응용 프로그램을 원한다. 응용 프로그램 서비스 로직은 유사 수 ...

    조금 실망의 종기이. (레디 스 구현) 귀하의 응용 프로그램에서 사용하는 캐싱. 레디 스 연결 / 오래된 폐쇄하거나 경우에, 당신은 바이 패스 캐싱 및 (아마도) (예를 들어, RDBMS)을 기본 데이터 저장소로 직접 이동에 응용 프로그램을 원한다. 응용 프로그램 서비스 로직은 유사 수 ...

    @Service
    class CustomerService ... {
    
        @Autowired
        private CustomerRepository customerRepo;
    
        protected CustomerRepository getCustomerRepo() {
            Assert.notNull(customerRepo, "The CustomerRepository was not initialized!");
            return customerRepo;
        }
    
        @Cacheable(value = "Customers")
        public Customer getCustomer(Long customerId) {
            return getCustomerRepo().load(customerId);
        }
        ...
    }
    

    모든 스프링 코어의 캐시 추상화의 문제는 캐시 "미스"값이 null 반환한다는 것입니다 확인 할 수있다. 이와 같이, 스프링 캐싱 인프라는 실제 서비스 방법 (즉 getCustomer)를 호출 진행한다. getCustomerRepo (). 부하의 반환에 유의하십시오 (고객 ID를) 호출은, 당신은 또한 지금 봄의 캐싱 인프라 시도 값을 캐시 경우를 처리 할 필요가있다.

    간단 유지의 정신에서 우리는 AOP없이 할 것입니다,하지만 당신은 잘 (선택)로 사용하여이 AOP를 달성 할 수 있어야한다.

    주류 (해야) 할 필요가, 같은를 SDR CacheManager 구현을 확장하는 "사용자 정의"RedisCacheManager입니다 ...

    package example;
    
    import org.springframework.cache.Cache;
    import org.springframework.data.redis.cache.RedisCacheManager;
    ...
    
    class MyCustomRedisCacheManager extends RedisCacheManager {
    
        public MyCustomerRedisCacheManager(RedisTemplate redisTemplate) {
            super(redisTemplate);
        }
    
        @Override
        public Cache getCache(String name) {
            return new RedisCacheWrapper(super.getCache(name));
        }
    
    
        protected static class RedisCacheWrapper implements Cache {
    
            private final Cache delegate;
    
            public RedisCacheWrapper(Cache redisCache) {
                Assert.notNull(redisCache, "'delegate' must not be null");
                this.delegate = redisCache;
            }
    
            @Override
            public Cache.ValueWrapper get(Object key) {
                try {
                  delegate.get(key);
                }
                catch (Exception e) {
                    return handleErrors(e);
                }
            }
    
            @Override
            public void put(Object key, Object value) {
                try {
                    delegate.put(key, value);
                }
                catch (Exception e) {
                    handleErrors(e);
                }
            }
    
            // implement clear(), evict(key), get(key, type), getName(), getNativeCache(), putIfAbsent(key, value) accordingly (delegating to the delegate).
    
            protected <T> T handleErrors(Exception e) throws Exception {
                if (e instanceof <some RedisConnection Exception type>) {
                    // log the connection problem
                    return null;
                }
                else if (<something different>) { // act appropriately }
                ...
                else {
                    throw e;
                }
            }
        }
    }
    

    레디 스를 사용할 수없는 경우에 따라서, 아마 당신이 할 수있는 최선의 문제를 기록하고 서비스 호출이 일어날 수 있도록 진행한다. 분명히,이 성능을 방해하지만 적어도 그것은 문제가 존재한다는 인식을 제고 할 것이다. 분명히,이보다 강력한 알림 시스템에 연결 할 수 있지만, 가능성의 원유 예입니다. 중요한 것은 다른 서비스 응용 프로그램 서비스에 의존하는 (예를 들어, 레디 스)가, 실패했을 수 있습니다 동안 서비스를 계속 사용할 수있다.

    레디 스의 존재와 함께 (내 이전의 설명 대)이 구현에서 나는 다음 전체 아니라 문제를 알고, 예외가 발생할 수 있도록 기본, 실제 RedisCache 구현에 위임하기로 결정했습니다, 그래서 당신은 적절하게 예외를 처리 할 수있다. 당신은 예외가 검사시 연결 문제와 관련이 있다는 확신한다면, 당신이 캐시 "미스"(즉, 나쁜 레디 스 연결 == 캐시 미스 인 것처럼 봄 캐싱 인프라를 진행할 수 있도록 "널 (null)"를 반환 할 수 있습니다, 이 경우).

    나는이 "사용자 정의"CacheManager의 GemFire에 대한 구현 및 중추의 고객 중 하나의 유사한 프로토 타입을 구축으로이 문제를 도움이 될 것 같은 것을 알고있다. 특정 UC에서 캐시 '미스는 "생산은 봄의 캐싱 추상화를 통해 GemFire에 연결하는 새로운 세 응용 프로그램 클라이언트의 혼합을 가지고 응용 프로그램 도메인 객체의"오래된 버전 "에 의해 트리거 될했다. 응용 프로그램 도메인 개체 필드는 예를 들어 응용 프로그램의 최신 버전으로 변경합니다.

    어쨌든,이 도움이 또는 더 많은 아이디어를 제공 바랍니다.

    건배!

  2. ==============================

    2.그래서, 난 또 다른 질문을 해결 오늘날 핵심 스프링 프레임 워크 캐싱 추상화 소스를 통해 발굴 된 그리고 CacheErrorHandler이 제대로 구현 된 경우, 다음 아마 문제가 레디 스 연결이 여전히 원하는 동작, 예를 들면 발생할 수 있습니다 보인다 (NULL 값의 반환에 트리거) 캐시 "미스".

    그래서, 난 또 다른 질문을 해결 오늘날 핵심 스프링 프레임 워크 캐싱 추상화 소스를 통해 발굴 된 그리고 CacheErrorHandler이 제대로 구현 된 경우, 다음 아마 문제가 레디 스 연결이 여전히 원하는 동작, 예를 들면 발생할 수 있습니다 보인다 (NULL 값의 반환에 트리거) 캐시 "미스".

    자세한 내용은 AbstractCacheInvoker 소스를 참조하십시오.

    cache.get (키)으로 인한 결함이 레디 스 연결하므로 예외 핸들러가 호출된다에 예외가 발생한다 ..

    catch (RuntimeException e) {
        getErrorHandler().handleCacheGetError(e, cache, key);
        return null; // If the exception is handled, return a cache miss
    }
    

    CacheErrorHandler 제대로 캐시를 처리하는 경우, 널 (null) 값은 캐시 "미스"를 나타내는 반환됩니다 오류 "수"(그리고 / 예외를 던지기 다시하지 않습니다).

  3. ==============================

    3.당신에게 @ 존 블룸 감사드립니다. 봄 부팅에 내 솔루션은 다음이다.

    당신에게 @ 존 블룸 감사드립니다. 봄 부팅에 내 솔루션은 다음이다.

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cache.Cache;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.util.Assert;
    
    import java.util.concurrent.Callable;
    
    class CustomRedisCacheManager extends RedisCacheManager {
        private static Logger logger = LoggerFactory.getLogger(CustomRedisCacheManager.class);
    
        public CustomRedisCacheManager(RedisOperations redisOperations) {
            super(redisOperations);
        }
    
        @Override
        public Cache getCache(String name) {
            return new RedisCacheWrapper(super.getCache(name));
        }
    
    
        protected static 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 Cache.ValueWrapper get(Object key) {
                try {
                    return delegate.get(key);
                } catch (Exception e) {
                    return handleException(e);
                }
            }
    
            @Override
            public <T> T get(Object o, Class<T> aClass) {
                try {
                    return delegate.get(o, aClass);
                } 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) {
                logger.error("handleException", e);
                return null;
            }
        }
    }
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.core.RedisTemplate;
    
    @Configuration
    public class RedisConfig {
        @Bean
        public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
            CustomRedisCacheManager redisCacheManager = new CustomRedisCacheManager(redisTemplate);
            redisCacheManager.setUsePrefix(true);
            return redisCacheManager;
        }
    }
    
  4. ==============================

    4.실제로 내 응답 씨 @Vivek 아 디트에 관한 것이다 - 나는 같은 문제에 직면 : 새로운 API를 스프링 데이터 레디 스 및 RedisTemplate 당 RedisCacheManager을 구성하지. 유일한 옵션 - @ 존 블룸의 제안에 따라이 - 사용 측면이었다. 그리고 아래에있는 내 코드입니다.

    실제로 내 응답 씨 @Vivek 아 디트에 관한 것이다 - 나는 같은 문제에 직면 : 새로운 API를 스프링 데이터 레디 스 및 RedisTemplate 당 RedisCacheManager을 구성하지. 유일한 옵션 - @ 존 블룸의 제안에 따라이 - 사용 측면이었다. 그리고 아래에있는 내 코드입니다.

    @Aspect
    @Component
    public class FailoverRedisCacheAspect {
    
        private static class FailoverRedisCache extends RedisCache {
    
            protected FailoverRedisCache(RedisCache redisCache) {
                super(redisCache.getName(), redisCache.getNativeCache(), redisCache.getCacheConfiguration());
            }
    
            @Override
            public <T> T get(Object key, Callable<T> valueLoader) {
                try {
                    return super.get(key, valueLoader);
                } catch (RuntimeException ex) {
                    return valueFromLoader(key, valueLoader);
                }
            }
    
            private <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
                try {
                    return valueLoader.call();
                } catch (Exception e) {
                    throw new ValueRetrievalException(key, valueLoader, e);
                }
            }
        }
    
        @Around("execution(* org.springframework.cache.support.AbstractCacheManager.getCache (..))")
        public Cache beforeSampleCreation(ProceedingJoinPoint proceedingJoinPoint) {
            try {
                Cache cache = (Cache) proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
                if (cache instanceof RedisCache) {
                    return new FailoverRedisCache((RedisCache) cache);
                } else {
                    return cache;
                }
            } catch (Throwable ex) {
                return null;
            }
        }
    }
    

    모든 합리적인 시나리오에 잘 작동합니다 :

    편집 : - 전용 "수"를, 나는 FailoverRedisCache마다 하나의 캐시 히트를 재차 생성 좋아하지 않는다 - 코드가 더 POC처럼지도가 있어야한다.

  5. ==============================

    5.RedisCacheManager 인 후드 아래 및 레디 스에 대한 기본 CacheManager에 코어 SF 대리자가 지원 JSR-107 JCache 주석과 함께 모든 코어 스프링 프레임 워크 캐시 추상화 주석 (예컨대 @Cacheable).

    RedisCacheManager 인 후드 아래 및 레디 스에 대한 기본 CacheManager에 코어 SF 대리자가 지원 JSR-107 JCache 주석과 함께 모든 코어 스프링 프레임 워크 캐시 추상화 주석 (예컨대 @Cacheable).

    여기에 비슷한 스프링 XML 설정 메타 데이터의 RedisCacheManager을 구성합니다.

    하나 개의 접근법은 각 (레디 스) CacheManger 동작의 연결 상태를 확인하기 위해 (간접적 RedisTemplate에서)를 RedisConnection을 사용 (레디 스) CacheManager위한 AOP 프록시를 작성하는 것이다.

    연결이 실패, 또는 ​​표준 캐시 작전을 위해 폐쇄되는 경우, (레디 스) CacheManager 따라서를 통과, 항상 (엔트리에 캐시 미스를 나타내는)를 돌려줍니다 getCache (문자열 이름)에 대한 RedisCache의 인스턴스를 반환 할 수 기본 데이터 저장소.

    내가 레디 스 (또는 SDR)이 있지만,이 일을하고 아마 당신에게 당신의 자신의 몇 십오를 제공해야 모든 일에 전문가가 아니라 나처럼 어쩌면 더 나은 방법이있다 처리 할 수 ​​있습니다.

    건배.

  6. ==============================

    6.나는 같은 문제를했지만, 불행하게도, 위의 방법 중 어느 것도 나를 위해 작동하지 않습니다. 나는 문제 점검 및 레디 스에 아무 관련이 없었다 경우 실행 명령이 시간 초과되지 않습니다 것을 발견했다. 그래서 나는 해결책 상추 라이브러리를 공부하기 시작합니다. 나는 아무 관련이 없을 때 명령을 거부하여 문제를 해결할 수 :

    나는 같은 문제를했지만, 불행하게도, 위의 방법 중 어느 것도 나를 위해 작동하지 않습니다. 나는 문제 점검 및 레디 스에 아무 관련이 없었다 경우 실행 명령이 시간 초과되지 않습니다 것을 발견했다. 그래서 나는 해결책 상추 라이브러리를 공부하기 시작합니다. 나는 아무 관련이 없을 때 명령을 거부하여 문제를 해결할 수 :

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory()
    {
        final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofSeconds(10)).build();
        ClientOptions clientOptions = ClientOptions.builder()
                .socketOptions(socketOptions)
                .autoReconnect(true)
                .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                .build();
    
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .commandTimeout(Duration.ofSeconds(10))
                .clientOptions(clientOptions).build();
    
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(this.host, this.port);
        return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
    
    }
    
  7. from https://stackoverflow.com/questions/29003786/how-to-disable-redis-caching-at-run-time-if-redis-connection-failed by cc-by-sa and MIT license